MediaWiki  1.31.0
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  // phpcs:ignore 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 
103  # Regular expression for a non-newline space
104  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
105 
106  # Flags for preprocessToDom
107  const PTD_FOR_INCLUSION = 1;
108 
109  # Allowed values for $this->mOutputType
110  # Parameter to startExternalParse().
111  const OT_HTML = 1; # like parse()
112  const OT_WIKI = 2; # like preSaveTransform()
113  const OT_PREPROCESS = 3; # like preprocess()
114  const OT_MSG = 3;
115  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
116 
134  const MARKER_SUFFIX = "-QINU`\"'\x7f";
135  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
136 
137  # Markers used for wrapping the table of contents
138  const TOC_START = '<mw:toc>';
139  const TOC_END = '</mw:toc>';
140 
141  # Persistent:
142  public $mTagHooks = [];
143  public $mTransparentTagHooks = [];
144  public $mFunctionHooks = [];
145  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
146  public $mFunctionTagHooks = [];
147  public $mStripList = [];
148  public $mDefaultStripList = [];
149  public $mVarCache = [];
150  public $mImageParams = [];
151  public $mImageParamsMagicArray = [];
152  public $mMarkerIndex = 0;
153  public $mFirstCall = true;
154 
155  # Initialised by initialiseVariables()
156 
160  public $mVariables;
161 
165  public $mSubstWords;
166  # Initialised in constructor
167  public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
168 
169  # Initialized in getPreprocessor()
170 
171  public $mPreprocessor;
172 
173  # Cleared with clearState():
174 
177  public $mOutput;
178  public $mAutonumber;
179 
183  public $mStripState;
184 
185  public $mIncludeCount;
189  public $mLinkHolders;
190 
191  public $mLinkID;
192  public $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth;
193  public $mDefaultSort;
194  public $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
195  public $mExpensiveFunctionCount; # number of expensive parser function calls
196  public $mShowToc, $mForceTocPosition;
197 
201  public $mUser; # User object; only used when doing pre-save transform
202 
203  # Temporary
204  # These are variables reset at least once per parse regardless of $clearState
205 
209  public $mOptions;
210 
214  public $mTitle; # Title context, used for self-link rendering and similar things
215  public $mOutputType; # Output type, one of the OT_xxx constants
216  public $ot; # Shortcut alias, see setOutputType()
217  public $mRevisionObject; # The revision object of the specified revision ID
218  public $mRevisionId; # ID to display in {{REVISIONID}} tags
219  public $mRevisionTimestamp; # The timestamp of the specified revision ID
220  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
221  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
222  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
223  public $mInputSize = false; # For {{PAGESIZE}} on current page.
224 
229  public $mUniqPrefix = self::MARKER_PREFIX;
230 
236  public $mLangLinkLanguages;
237 
244  public $currentRevisionCache;
245 
250  public $mInParse = false;
251 
253  protected $mProfiler;
254 
258  protected $mLinkRenderer;
259 
263  public function __construct( $conf = [] ) {
264  $this->mConf = $conf;
265  $this->mUrlProtocols = wfUrlProtocols();
266  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
267  self::EXT_LINK_ADDR .
268  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
269  if ( isset( $conf['preprocessorClass'] ) ) {
270  $this->mPreprocessorClass = $conf['preprocessorClass'];
271  } elseif ( defined( 'HPHP_VERSION' ) ) {
272  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
273  $this->mPreprocessorClass = Preprocessor_Hash::class;
274  } elseif ( extension_loaded( 'domxml' ) ) {
275  # PECL extension that conflicts with the core DOM extension (T15770)
276  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
277  $this->mPreprocessorClass = Preprocessor_Hash::class;
278  } elseif ( extension_loaded( 'dom' ) ) {
279  $this->mPreprocessorClass = Preprocessor_DOM::class;
280  } else {
281  $this->mPreprocessorClass = Preprocessor_Hash::class;
282  }
283  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
284  }
285 
289  public function __destruct() {
290  if ( isset( $this->mLinkHolders ) ) {
291  unset( $this->mLinkHolders );
292  }
293  foreach ( $this as $name => $value ) {
294  unset( $this->$name );
295  }
296  }
297 
301  public function __clone() {
302  $this->mInParse = false;
303 
304  // T58226: When you create a reference "to" an object field, that
305  // makes the object field itself be a reference too (until the other
306  // reference goes out of scope). When cloning, any field that's a
307  // reference is copied as a reference in the new object. Both of these
308  // are defined PHP5 behaviors, as inconvenient as it is for us when old
309  // hooks from PHP4 days are passing fields by reference.
310  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
311  // Make a non-reference copy of the field, then rebind the field to
312  // reference the new copy.
313  $tmp = $this->$k;
314  $this->$k =& $tmp;
315  unset( $tmp );
316  }
317 
318  Hooks::run( 'ParserCloned', [ $this ] );
319  }
320 
324  public function firstCallInit() {
325  if ( !$this->mFirstCall ) {
326  return;
327  }
328  $this->mFirstCall = false;
329 
331  CoreTagHooks::register( $this );
332  $this->initialiseVariables();
333 
334  // Avoid PHP 7.1 warning from passing $this by reference
335  $parser = $this;
336  Hooks::run( 'ParserFirstCallInit', [ &$parser ] );
337  }
338 
344  public function clearState() {
345  if ( $this->mFirstCall ) {
346  $this->firstCallInit();
347  }
348  $this->mOutput = new ParserOutput;
349  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
350  $this->mAutonumber = 0;
351  $this->mIncludeCount = [];
352  $this->mLinkHolders = new LinkHolderArray( $this );
353  $this->mLinkID = 0;
354  $this->mRevisionObject = $this->mRevisionTimestamp =
355  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
356  $this->mVarCache = [];
357  $this->mUser = null;
358  $this->mLangLinkLanguages = [];
359  $this->currentRevisionCache = null;
360 
361  $this->mStripState = new StripState( $this );
362 
363  # Clear these on every parse, T6549
364  $this->mTplRedirCache = $this->mTplDomCache = [];
365 
366  $this->mShowToc = true;
367  $this->mForceTocPosition = false;
368  $this->mIncludeSizes = [
369  'post-expand' => 0,
370  'arg' => 0,
371  ];
372  $this->mPPNodeCount = 0;
373  $this->mGeneratedPPNodeCount = 0;
374  $this->mHighestExpansionDepth = 0;
375  $this->mDefaultSort = false;
376  $this->mHeadings = [];
377  $this->mDoubleUnderscores = [];
378  $this->mExpensiveFunctionCount = 0;
379 
380  # Fix cloning
381  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
382  $this->mPreprocessor = null;
383  }
384 
385  $this->mProfiler = new SectionProfiler();
386 
387  // Avoid PHP 7.1 warning from passing $this by reference
388  $parser = $this;
389  Hooks::run( 'ParserClearState', [ &$parser ] );
390  }
391 
404  public function parse(
406  $linestart = true, $clearState = true, $revid = null
407  ) {
408  if ( $clearState ) {
409  // We use U+007F DELETE to construct strip markers, so we have to make
410  // sure that this character does not occur in the input text.
411  $text = strtr( $text, "\x7f", "?" );
412  $magicScopeVariable = $this->lock();
413  }
414  // Strip U+0000 NULL (T159174)
415  $text = str_replace( "\000", '', $text );
416 
417  $this->startParse( $title, $options, self::OT_HTML, $clearState );
418 
419  $this->currentRevisionCache = null;
420  $this->mInputSize = strlen( $text );
421  if ( $this->mOptions->getEnableLimitReport() ) {
422  $this->mOutput->resetParseStartTime();
423  }
424 
425  $oldRevisionId = $this->mRevisionId;
426  $oldRevisionObject = $this->mRevisionObject;
427  $oldRevisionTimestamp = $this->mRevisionTimestamp;
428  $oldRevisionUser = $this->mRevisionUser;
429  $oldRevisionSize = $this->mRevisionSize;
430  if ( $revid !== null ) {
431  $this->mRevisionId = $revid;
432  $this->mRevisionObject = null;
433  $this->mRevisionTimestamp = null;
434  $this->mRevisionUser = null;
435  $this->mRevisionSize = null;
436  }
437 
438  // Avoid PHP 7.1 warning from passing $this by reference
439  $parser = $this;
440  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
441  # No more strip!
442  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
443  $text = $this->internalParse( $text );
444  Hooks::run( 'ParserAfterParse', [ &$parser, &$text, &$this->mStripState ] );
445 
446  $text = $this->internalParseHalfParsed( $text, true, $linestart );
447 
455  if ( !( $options->getDisableTitleConversion()
456  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
457  || isset( $this->mDoubleUnderscores['notitleconvert'] )
458  || $this->mOutput->getDisplayTitle() !== false )
459  ) {
460  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
461  if ( $convruletitle ) {
462  $this->mOutput->setTitleText( $convruletitle );
463  } else {
464  $titleText = $this->getConverterLanguage()->convertTitle( $title );
465  $this->mOutput->setTitleText( $titleText );
466  }
467  }
468 
469  # Compute runtime adaptive expiry if set
470  $this->mOutput->finalizeAdaptiveCacheExpiry();
471 
472  # Warn if too many heavyweight parser functions were used
473  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
474  $this->limitationWarn( 'expensive-parserfunction',
475  $this->mExpensiveFunctionCount,
476  $this->mOptions->getExpensiveParserFunctionLimit()
477  );
478  }
479 
480  # Information on limits, for the benefit of users who try to skirt them
481  if ( $this->mOptions->getEnableLimitReport() ) {
482  $text .= $this->makeLimitReport();
483  }
484 
485  # Wrap non-interface parser output in a <div> so it can be targeted
486  # with CSS (T37247)
487  $class = $this->mOptions->getWrapOutputClass();
488  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
489  $text = Html::rawElement( 'div', [ 'class' => $class ], $text );
490  }
491 
492  $this->mOutput->setText( $text );
493 
494  $this->mRevisionId = $oldRevisionId;
495  $this->mRevisionObject = $oldRevisionObject;
496  $this->mRevisionTimestamp = $oldRevisionTimestamp;
497  $this->mRevisionUser = $oldRevisionUser;
498  $this->mRevisionSize = $oldRevisionSize;
499  $this->mInputSize = false;
500  $this->currentRevisionCache = null;
501 
502  return $this->mOutput;
503  }
504 
511  protected function makeLimitReport() {
513 
514  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
515 
516  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
517  if ( $cpuTime !== null ) {
518  $this->mOutput->setLimitReportData( 'limitreport-cputime',
519  sprintf( "%.3f", $cpuTime )
520  );
521  }
522 
523  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
524  $this->mOutput->setLimitReportData( 'limitreport-walltime',
525  sprintf( "%.3f", $wallTime )
526  );
527 
528  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
529  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
530  );
531  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
532  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
533  );
534  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
535  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
536  );
537  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
538  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
539  );
540  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
541  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
542  );
543  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
544  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
545  );
546 
547  foreach ( $this->mStripState->getLimitReport() as list( $key, $value ) ) {
548  $this->mOutput->setLimitReportData( $key, $value );
549  }
550 
551  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
552 
553  $limitReport = "NewPP limit report\n";
554  if ( $wgShowHostnames ) {
555  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
556  }
557  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
558  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
559  $limitReport .= 'Dynamic content: ' .
560  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
561  "\n";
562 
563  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
564  if ( Hooks::run( 'ParserLimitReportFormat',
565  [ $key, &$value, &$limitReport, false, false ]
566  ) ) {
567  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
568  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
569  ->inLanguage( 'en' )->useDatabase( false );
570  if ( !$valueMsg->exists() ) {
571  $valueMsg = new RawMessage( '$1' );
572  }
573  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
574  $valueMsg->params( $value );
575  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
576  }
577  }
578  }
579  // Since we're not really outputting HTML, decode the entities and
580  // then re-encode the things that need hiding inside HTML comments.
581  $limitReport = htmlspecialchars_decode( $limitReport );
582  // Run deprecated hook
583  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ], '1.22' );
584 
585  // Sanitize for comment. Note '‐' in the replacement is U+2010,
586  // which looks much like the problematic '-'.
587  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
588  $text = "\n<!-- \n$limitReport-->\n";
589 
590  // Add on template profiling data in human/machine readable way
591  $dataByFunc = $this->mProfiler->getFunctionStats();
592  uasort( $dataByFunc, function ( $a, $b ) {
593  return $a['real'] < $b['real']; // descending order
594  } );
595  $profileReport = [];
596  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
597  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
598  $item['%real'], $item['real'], $item['calls'],
599  htmlspecialchars( $item['name'] ) );
600  }
601  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
602  $text .= implode( "\n", $profileReport ) . "\n-->\n";
603 
604  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
605 
606  // Add other cache related metadata
607  if ( $wgShowHostnames ) {
608  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
609  }
610  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
611  $this->mOutput->getCacheTime() );
612  $this->mOutput->setLimitReportData( 'cachereport-ttl',
613  $this->mOutput->getCacheExpiry() );
614  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
615  $this->mOutput->hasDynamicContent() );
616 
617  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
618  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
619  $this->mTitle->getPrefixedDBkey() );
620  }
621  return $text;
622  }
623 
646  public function recursiveTagParse( $text, $frame = false ) {
647  // Avoid PHP 7.1 warning from passing $this by reference
648  $parser = $this;
649  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
650  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
651  $text = $this->internalParse( $text, false, $frame );
652  return $text;
653  }
654 
672  public function recursiveTagParseFully( $text, $frame = false ) {
673  $text = $this->recursiveTagParse( $text, $frame );
674  $text = $this->internalParseHalfParsed( $text, false );
675  return $text;
676  }
677 
689  public function preprocess( $text, Title $title = null,
690  ParserOptions $options, $revid = null, $frame = false
691  ) {
692  $magicScopeVariable = $this->lock();
693  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
694  if ( $revid !== null ) {
695  $this->mRevisionId = $revid;
696  }
697  // Avoid PHP 7.1 warning from passing $this by reference
698  $parser = $this;
699  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
700  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
701  $text = $this->replaceVariables( $text, $frame );
702  $text = $this->mStripState->unstripBoth( $text );
703  return $text;
704  }
705 
715  public function recursivePreprocess( $text, $frame = false ) {
716  $text = $this->replaceVariables( $text, $frame );
717  $text = $this->mStripState->unstripBoth( $text );
718  return $text;
719  }
720 
734  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
735  $msg = new RawMessage( $text );
736  $text = $msg->params( $params )->plain();
737 
738  # Parser (re)initialisation
739  $magicScopeVariable = $this->lock();
740  $this->startParse( $title, $options, self::OT_PLAIN, true );
741 
743  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
744  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
745  $text = $this->mStripState->unstripBoth( $text );
746  return $text;
747  }
748 
755  public function setUser( $user ) {
756  $this->mUser = $user;
757  }
758 
764  public function setTitle( $t ) {
765  if ( !$t ) {
766  $t = Title::newFromText( 'NO TITLE' );
767  }
768 
769  if ( $t->hasFragment() ) {
770  # Strip the fragment to avoid various odd effects
771  $this->mTitle = $t->createFragmentTarget( '' );
772  } else {
773  $this->mTitle = $t;
774  }
775  }
776 
782  public function getTitle() {
783  return $this->mTitle;
784  }
785 
792  public function Title( $x = null ) {
793  return wfSetVar( $this->mTitle, $x );
794  }
795 
801  public function setOutputType( $ot ) {
802  $this->mOutputType = $ot;
803  # Shortcut alias
804  $this->ot = [
805  'html' => $ot == self::OT_HTML,
806  'wiki' => $ot == self::OT_WIKI,
807  'pre' => $ot == self::OT_PREPROCESS,
808  'plain' => $ot == self::OT_PLAIN,
809  ];
810  }
811 
818  public function OutputType( $x = null ) {
819  return wfSetVar( $this->mOutputType, $x );
820  }
821 
827  public function getOutput() {
828  return $this->mOutput;
829  }
830 
836  public function getOptions() {
837  return $this->mOptions;
838  }
839 
846  public function Options( $x = null ) {
847  return wfSetVar( $this->mOptions, $x );
848  }
849 
853  public function nextLinkID() {
854  return $this->mLinkID++;
855  }
856 
860  public function setLinkID( $id ) {
861  $this->mLinkID = $id;
862  }
863 
868  public function getFunctionLang() {
869  return $this->getTargetLanguage();
870  }
871 
881  public function getTargetLanguage() {
882  $target = $this->mOptions->getTargetLanguage();
883 
884  if ( $target !== null ) {
885  return $target;
886  } elseif ( $this->mOptions->getInterfaceMessage() ) {
887  return $this->mOptions->getUserLangObj();
888  } elseif ( is_null( $this->mTitle ) ) {
889  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
890  }
891 
892  return $this->mTitle->getPageLanguage();
893  }
894 
899  public function getConverterLanguage() {
900  return $this->getTargetLanguage();
901  }
902 
909  public function getUser() {
910  if ( !is_null( $this->mUser ) ) {
911  return $this->mUser;
912  }
913  return $this->mOptions->getUser();
914  }
915 
921  public function getPreprocessor() {
922  if ( !isset( $this->mPreprocessor ) ) {
923  $class = $this->mPreprocessorClass;
924  $this->mPreprocessor = new $class( $this );
925  }
926  return $this->mPreprocessor;
927  }
928 
935  public function getLinkRenderer() {
936  if ( !$this->mLinkRenderer ) {
937  $this->mLinkRenderer = MediaWikiServices::getInstance()
938  ->getLinkRendererFactory()->create();
939  $this->mLinkRenderer->setStubThreshold(
940  $this->getOptions()->getStubThreshold()
941  );
942  }
943 
944  return $this->mLinkRenderer;
945  }
946 
966  public static function extractTagsAndParams( $elements, $text, &$matches ) {
967  static $n = 1;
968  $stripped = '';
969  $matches = [];
970 
971  $taglist = implode( '|', $elements );
972  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
973 
974  while ( $text != '' ) {
975  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
976  $stripped .= $p[0];
977  if ( count( $p ) < 5 ) {
978  break;
979  }
980  if ( count( $p ) > 5 ) {
981  # comment
982  $element = $p[4];
983  $attributes = '';
984  $close = '';
985  $inside = $p[5];
986  } else {
987  # tag
988  $element = $p[1];
989  $attributes = $p[2];
990  $close = $p[3];
991  $inside = $p[4];
992  }
993 
994  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
995  $stripped .= $marker;
996 
997  if ( $close === '/>' ) {
998  # Empty element tag, <tag />
999  $content = null;
1000  $text = $inside;
1001  $tail = null;
1002  } else {
1003  if ( $element === '!--' ) {
1004  $end = '/(-->)/';
1005  } else {
1006  $end = "/(<\\/$element\\s*>)/i";
1007  }
1008  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1009  $content = $q[0];
1010  if ( count( $q ) < 3 ) {
1011  # No end tag -- let it run out to the end of the text.
1012  $tail = '';
1013  $text = '';
1014  } else {
1015  $tail = $q[1];
1016  $text = $q[2];
1017  }
1018  }
1019 
1020  $matches[$marker] = [ $element,
1021  $content,
1022  Sanitizer::decodeTagAttributes( $attributes ),
1023  "<$element$attributes$close$content$tail" ];
1024  }
1025  return $stripped;
1026  }
1027 
1033  public function getStripList() {
1034  return $this->mStripList;
1035  }
1036 
1046  public function insertStripItem( $text ) {
1047  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1048  $this->mMarkerIndex++;
1049  $this->mStripState->addGeneral( $marker, $text );
1050  return $marker;
1051  }
1052 
1060  public function doTableStuff( $text ) {
1061  $lines = StringUtils::explode( "\n", $text );
1062  $out = '';
1063  $td_history = []; # Is currently a td tag open?
1064  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1065  $tr_history = []; # Is currently a tr tag open?
1066  $tr_attributes = []; # history of tr attributes
1067  $has_opened_tr = []; # Did this table open a <tr> element?
1068  $indent_level = 0; # indent level of the table
1069 
1070  foreach ( $lines as $outLine ) {
1071  $line = trim( $outLine );
1072 
1073  if ( $line === '' ) { # empty line, go to next line
1074  $out .= $outLine . "\n";
1075  continue;
1076  }
1077 
1078  $first_character = $line[0];
1079  $first_two = substr( $line, 0, 2 );
1080  $matches = [];
1081 
1082  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1083  # First check if we are starting a new table
1084  $indent_level = strlen( $matches[1] );
1085 
1086  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1087  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1088 
1089  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1090  array_push( $td_history, false );
1091  array_push( $last_tag_history, '' );
1092  array_push( $tr_history, false );
1093  array_push( $tr_attributes, '' );
1094  array_push( $has_opened_tr, false );
1095  } elseif ( count( $td_history ) == 0 ) {
1096  # Don't do any of the following
1097  $out .= $outLine . "\n";
1098  continue;
1099  } elseif ( $first_two === '|}' ) {
1100  # We are ending a table
1101  $line = '</table>' . substr( $line, 2 );
1102  $last_tag = array_pop( $last_tag_history );
1103 
1104  if ( !array_pop( $has_opened_tr ) ) {
1105  $line = "<tr><td></td></tr>{$line}";
1106  }
1107 
1108  if ( array_pop( $tr_history ) ) {
1109  $line = "</tr>{$line}";
1110  }
1111 
1112  if ( array_pop( $td_history ) ) {
1113  $line = "</{$last_tag}>{$line}";
1114  }
1115  array_pop( $tr_attributes );
1116  if ( $indent_level > 0 ) {
1117  $outLine = rtrim( $line ) . str_repeat( '</dd></dl>', $indent_level );
1118  } else {
1119  $outLine = $line;
1120  }
1121  } elseif ( $first_two === '|-' ) {
1122  # Now we have a table row
1123  $line = preg_replace( '#^\|-+#', '', $line );
1124 
1125  # Whats after the tag is now only attributes
1126  $attributes = $this->mStripState->unstripBoth( $line );
1127  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1128  array_pop( $tr_attributes );
1129  array_push( $tr_attributes, $attributes );
1130 
1131  $line = '';
1132  $last_tag = array_pop( $last_tag_history );
1133  array_pop( $has_opened_tr );
1134  array_push( $has_opened_tr, true );
1135 
1136  if ( array_pop( $tr_history ) ) {
1137  $line = '</tr>';
1138  }
1139 
1140  if ( array_pop( $td_history ) ) {
1141  $line = "</{$last_tag}>{$line}";
1142  }
1143 
1144  $outLine = $line;
1145  array_push( $tr_history, false );
1146  array_push( $td_history, false );
1147  array_push( $last_tag_history, '' );
1148  } elseif ( $first_character === '|'
1149  || $first_character === '!'
1150  || $first_two === '|+'
1151  ) {
1152  # This might be cell elements, td, th or captions
1153  if ( $first_two === '|+' ) {
1154  $first_character = '+';
1155  $line = substr( $line, 2 );
1156  } else {
1157  $line = substr( $line, 1 );
1158  }
1159 
1160  // Implies both are valid for table headings.
1161  if ( $first_character === '!' ) {
1162  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1163  }
1164 
1165  # Split up multiple cells on the same line.
1166  # FIXME : This can result in improper nesting of tags processed
1167  # by earlier parser steps.
1168  $cells = explode( '||', $line );
1169 
1170  $outLine = '';
1171 
1172  # Loop through each table cell
1173  foreach ( $cells as $cell ) {
1174  $previous = '';
1175  if ( $first_character !== '+' ) {
1176  $tr_after = array_pop( $tr_attributes );
1177  if ( !array_pop( $tr_history ) ) {
1178  $previous = "<tr{$tr_after}>\n";
1179  }
1180  array_push( $tr_history, true );
1181  array_push( $tr_attributes, '' );
1182  array_pop( $has_opened_tr );
1183  array_push( $has_opened_tr, true );
1184  }
1185 
1186  $last_tag = array_pop( $last_tag_history );
1187 
1188  if ( array_pop( $td_history ) ) {
1189  $previous = "</{$last_tag}>\n{$previous}";
1190  }
1191 
1192  if ( $first_character === '|' ) {
1193  $last_tag = 'td';
1194  } elseif ( $first_character === '!' ) {
1195  $last_tag = 'th';
1196  } elseif ( $first_character === '+' ) {
1197  $last_tag = 'caption';
1198  } else {
1199  $last_tag = '';
1200  }
1201 
1202  array_push( $last_tag_history, $last_tag );
1203 
1204  # A cell could contain both parameters and data
1205  $cell_data = explode( '|', $cell, 2 );
1206 
1207  # T2553: Note that a '|' inside an invalid link should not
1208  # be mistaken as delimiting cell parameters
1209  # Bug T153140: Neither should language converter markup.
1210  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1211  $cell = "{$previous}<{$last_tag}>" . trim( $cell );
1212  } elseif ( count( $cell_data ) == 1 ) {
1213  // Whitespace in cells is trimmed
1214  $cell = "{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1215  } else {
1216  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1217  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1218  // Whitespace in cells is trimmed
1219  $cell = "{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1220  }
1221 
1222  $outLine .= $cell;
1223  array_push( $td_history, true );
1224  }
1225  }
1226  $out .= $outLine . "\n";
1227  }
1228 
1229  # Closing open td, tr && table
1230  while ( count( $td_history ) > 0 ) {
1231  if ( array_pop( $td_history ) ) {
1232  $out .= "</td>\n";
1233  }
1234  if ( array_pop( $tr_history ) ) {
1235  $out .= "</tr>\n";
1236  }
1237  if ( !array_pop( $has_opened_tr ) ) {
1238  $out .= "<tr><td></td></tr>\n";
1239  }
1240 
1241  $out .= "</table>\n";
1242  }
1243 
1244  # Remove trailing line-ending (b/c)
1245  if ( substr( $out, -1 ) === "\n" ) {
1246  $out = substr( $out, 0, -1 );
1247  }
1248 
1249  # special case: don't return empty table
1250  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1251  $out = '';
1252  }
1253 
1254  return $out;
1255  }
1256 
1269  public function internalParse( $text, $isMain = true, $frame = false ) {
1270  $origText = $text;
1271 
1272  // Avoid PHP 7.1 warning from passing $this by reference
1273  $parser = $this;
1274 
1275  # Hook to suspend the parser in this state
1276  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1277  return $text;
1278  }
1279 
1280  # if $frame is provided, then use $frame for replacing any variables
1281  if ( $frame ) {
1282  # use frame depth to infer how include/noinclude tags should be handled
1283  # depth=0 means this is the top-level document; otherwise it's an included document
1284  if ( !$frame->depth ) {
1285  $flag = 0;
1286  } else {
1287  $flag = self::PTD_FOR_INCLUSION;
1288  }
1289  $dom = $this->preprocessToDom( $text, $flag );
1290  $text = $frame->expand( $dom );
1291  } else {
1292  # if $frame is not provided, then use old-style replaceVariables
1293  $text = $this->replaceVariables( $text );
1294  }
1295 
1296  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1297  $text = Sanitizer::removeHTMLtags(
1298  $text,
1299  [ $this, 'attributeStripCallback' ],
1300  false,
1301  array_keys( $this->mTransparentTagHooks ),
1302  [],
1303  [ $this, 'addTrackingCategory' ]
1304  );
1305  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1306 
1307  # Tables need to come after variable replacement for things to work
1308  # properly; putting them before other transformations should keep
1309  # exciting things like link expansions from showing up in surprising
1310  # places.
1311  $text = $this->doTableStuff( $text );
1312 
1313  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1314 
1315  $text = $this->doDoubleUnderscore( $text );
1316 
1317  $text = $this->doHeadings( $text );
1318  $text = $this->replaceInternalLinks( $text );
1319  $text = $this->doAllQuotes( $text );
1320  $text = $this->replaceExternalLinks( $text );
1321 
1322  # replaceInternalLinks may sometimes leave behind
1323  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1324  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1325 
1326  $text = $this->doMagicLinks( $text );
1327  $text = $this->formatHeadings( $text, $origText, $isMain );
1328 
1329  return $text;
1330  }
1331 
1341  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1342  $text = $this->mStripState->unstripGeneral( $text );
1343 
1344  // Avoid PHP 7.1 warning from passing $this by reference
1345  $parser = $this;
1346 
1347  if ( $isMain ) {
1348  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1349  }
1350 
1351  # Clean up special characters, only run once, next-to-last before doBlockLevels
1352  $fixtags = [
1353  # French spaces, last one Guillemet-left
1354  # only if there is something before the space
1355  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1356  # french spaces, Guillemet-right
1357  '/(\\302\\253) /' => '\\1&#160;',
1358  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1359  ];
1360  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1361 
1362  $text = $this->doBlockLevels( $text, $linestart );
1363 
1364  $this->replaceLinkHolders( $text );
1365 
1373  if ( !( $this->mOptions->getDisableContentConversion()
1374  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1375  ) {
1376  if ( !$this->mOptions->getInterfaceMessage() ) {
1377  # The position of the convert() call should not be changed. it
1378  # assumes that the links are all replaced and the only thing left
1379  # is the <nowiki> mark.
1380  $text = $this->getConverterLanguage()->convert( $text );
1381  }
1382  }
1383 
1384  $text = $this->mStripState->unstripNoWiki( $text );
1385 
1386  if ( $isMain ) {
1387  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1388  }
1389 
1390  $text = $this->replaceTransparentTags( $text );
1391  $text = $this->mStripState->unstripGeneral( $text );
1392 
1393  $text = Sanitizer::normalizeCharReferences( $text );
1394 
1395  if ( MWTidy::isEnabled() ) {
1396  if ( $this->mOptions->getTidy() ) {
1397  $text = MWTidy::tidy( $text );
1398  }
1399  } else {
1400  # attempt to sanitize at least some nesting problems
1401  # (T4702 and quite a few others)
1402  $tidyregs = [
1403  # ''Something [http://www.cool.com cool''] -->
1404  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1405  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1406  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1407  # fix up an anchor inside another anchor, only
1408  # at least for a single single nested link (T5695)
1409  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1410  '\\1\\2</a>\\3</a>\\1\\4</a>',
1411  # fix div inside inline elements- doBlockLevels won't wrap a line which
1412  # contains a div, so fix it up here; replace
1413  # div with escaped text
1414  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1415  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1416  # remove empty italic or bold tag pairs, some
1417  # introduced by rules above
1418  '/<([bi])><\/\\1>/' => '',
1419  ];
1420 
1421  $text = preg_replace(
1422  array_keys( $tidyregs ),
1423  array_values( $tidyregs ),
1424  $text );
1425  }
1426 
1427  if ( $isMain ) {
1428  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1429  }
1430 
1431  return $text;
1432  }
1433 
1445  public function doMagicLinks( $text ) {
1446  $prots = wfUrlProtocolsWithoutProtRel();
1447  $urlChar = self::EXT_LINK_URL_CLASS;
1448  $addr = self::EXT_LINK_ADDR;
1449  $space = self::SPACE_NOT_NL; # non-newline space
1450  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1451  $spaces = "$space++"; # possessive match of 1 or more spaces
1452  $text = preg_replace_callback(
1453  '!(?: # Start cases
1454  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1455  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1456  (\b # m[3]: Free external links
1457  (?i:$prots)
1458  ($addr$urlChar*) # m[4]: Post-protocol path
1459  ) |
1460  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1461  ([0-9]+)\b |
1462  \bISBN $spaces ( # m[6]: ISBN, capture number
1463  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1464  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1465  [0-9Xx] # check digit
1466  )\b
1467  )!xu", [ $this, 'magicLinkCallback' ], $text );
1468  return $text;
1469  }
1470 
1476  public function magicLinkCallback( $m ) {
1477  if ( isset( $m[1] ) && $m[1] !== '' ) {
1478  # Skip anchor
1479  return $m[0];
1480  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1481  # Skip HTML element
1482  return $m[0];
1483  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1484  # Free external link
1485  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1486  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1487  # RFC or PMID
1488  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1489  if ( !$this->mOptions->getMagicRFCLinks() ) {
1490  return $m[0];
1491  }
1492  $keyword = 'RFC';
1493  $urlmsg = 'rfcurl';
1494  $cssClass = 'mw-magiclink-rfc';
1495  $trackingCat = 'magiclink-tracking-rfc';
1496  $id = $m[5];
1497  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1498  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1499  return $m[0];
1500  }
1501  $keyword = 'PMID';
1502  $urlmsg = 'pubmedurl';
1503  $cssClass = 'mw-magiclink-pmid';
1504  $trackingCat = 'magiclink-tracking-pmid';
1505  $id = $m[5];
1506  } else {
1507  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1508  substr( $m[0], 0, 20 ) . '"' );
1509  }
1510  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1511  $this->addTrackingCategory( $trackingCat );
1512  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1513  } elseif ( isset( $m[6] ) && $m[6] !== ''
1514  && $this->mOptions->getMagicISBNLinks()
1515  ) {
1516  # ISBN
1517  $isbn = $m[6];
1518  $space = self::SPACE_NOT_NL; # non-newline space
1519  $isbn = preg_replace( "/$space/", ' ', $isbn );
1520  $num = strtr( $isbn, [
1521  '-' => '',
1522  ' ' => '',
1523  'x' => 'X',
1524  ] );
1525  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1526  return $this->getLinkRenderer()->makeKnownLink(
1527  SpecialPage::getTitleFor( 'Booksources', $num ),
1528  "ISBN $isbn",
1529  [
1530  'class' => 'internal mw-magiclink-isbn',
1531  'title' => false // suppress title attribute
1532  ]
1533  );
1534  } else {
1535  return $m[0];
1536  }
1537  }
1538 
1548  public function makeFreeExternalLink( $url, $numPostProto ) {
1549  $trail = '';
1550 
1551  # The characters '<' and '>' (which were escaped by
1552  # removeHTMLtags()) should not be included in
1553  # URLs, per RFC 2396.
1554  # Make &nbsp; terminate a URL as well (bug T84937)
1555  $m2 = [];
1556  if ( preg_match(
1557  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1558  $url,
1559  $m2,
1560  PREG_OFFSET_CAPTURE
1561  ) ) {
1562  $trail = substr( $url, $m2[0][1] ) . $trail;
1563  $url = substr( $url, 0, $m2[0][1] );
1564  }
1565 
1566  # Move trailing punctuation to $trail
1567  $sep = ',;\.:!?';
1568  # If there is no left bracket, then consider right brackets fair game too
1569  if ( strpos( $url, '(' ) === false ) {
1570  $sep .= ')';
1571  }
1572 
1573  $urlRev = strrev( $url );
1574  $numSepChars = strspn( $urlRev, $sep );
1575  # Don't break a trailing HTML entity by moving the ; into $trail
1576  # This is in hot code, so use substr_compare to avoid having to
1577  # create a new string object for the comparison
1578  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1579  # more optimization: instead of running preg_match with a $
1580  # anchor, which can be slow, do the match on the reversed
1581  # string starting at the desired offset.
1582  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1583  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1584  $numSepChars--;
1585  }
1586  }
1587  if ( $numSepChars ) {
1588  $trail = substr( $url, -$numSepChars ) . $trail;
1589  $url = substr( $url, 0, -$numSepChars );
1590  }
1591 
1592  # Verify that we still have a real URL after trail removal, and
1593  # not just lone protocol
1594  if ( strlen( $trail ) >= $numPostProto ) {
1595  return $url . $trail;
1596  }
1597 
1598  $url = Sanitizer::cleanUrl( $url );
1599 
1600  # Is this an external image?
1601  $text = $this->maybeMakeExternalImage( $url );
1602  if ( $text === false ) {
1603  # Not an image, make a link
1604  $text = Linker::makeExternalLink( $url,
1605  $this->getConverterLanguage()->markNoConversion( $url, true ),
1606  true, 'free',
1607  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1608  # Register it in the output object...
1609  $this->mOutput->addExternalLink( $url );
1610  }
1611  return $text . $trail;
1612  }
1613 
1623  public function doHeadings( $text ) {
1624  for ( $i = 6; $i >= 1; --$i ) {
1625  $h = str_repeat( '=', $i );
1626  // Trim non-newline whitespace from headings
1627  // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2>
1628  $text = preg_replace( "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text );
1629  }
1630  return $text;
1631  }
1632 
1641  public function doAllQuotes( $text ) {
1642  $outtext = '';
1643  $lines = StringUtils::explode( "\n", $text );
1644  foreach ( $lines as $line ) {
1645  $outtext .= $this->doQuotes( $line ) . "\n";
1646  }
1647  $outtext = substr( $outtext, 0, -1 );
1648  return $outtext;
1649  }
1650 
1658  public function doQuotes( $text ) {
1659  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1660  $countarr = count( $arr );
1661  if ( $countarr == 1 ) {
1662  return $text;
1663  }
1664 
1665  // First, do some preliminary work. This may shift some apostrophes from
1666  // being mark-up to being text. It also counts the number of occurrences
1667  // of bold and italics mark-ups.
1668  $numbold = 0;
1669  $numitalics = 0;
1670  for ( $i = 1; $i < $countarr; $i += 2 ) {
1671  $thislen = strlen( $arr[$i] );
1672  // If there are ever four apostrophes, assume the first is supposed to
1673  // be text, and the remaining three constitute mark-up for bold text.
1674  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1675  if ( $thislen == 4 ) {
1676  $arr[$i - 1] .= "'";
1677  $arr[$i] = "'''";
1678  $thislen = 3;
1679  } elseif ( $thislen > 5 ) {
1680  // If there are more than 5 apostrophes in a row, assume they're all
1681  // text except for the last 5.
1682  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1683  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1684  $arr[$i] = "'''''";
1685  $thislen = 5;
1686  }
1687  // Count the number of occurrences of bold and italics mark-ups.
1688  if ( $thislen == 2 ) {
1689  $numitalics++;
1690  } elseif ( $thislen == 3 ) {
1691  $numbold++;
1692  } elseif ( $thislen == 5 ) {
1693  $numitalics++;
1694  $numbold++;
1695  }
1696  }
1697 
1698  // If there is an odd number of both bold and italics, it is likely
1699  // that one of the bold ones was meant to be an apostrophe followed
1700  // by italics. Which one we cannot know for certain, but it is more
1701  // likely to be one that has a single-letter word before it.
1702  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1703  $firstsingleletterword = -1;
1704  $firstmultiletterword = -1;
1705  $firstspace = -1;
1706  for ( $i = 1; $i < $countarr; $i += 2 ) {
1707  if ( strlen( $arr[$i] ) == 3 ) {
1708  $x1 = substr( $arr[$i - 1], -1 );
1709  $x2 = substr( $arr[$i - 1], -2, 1 );
1710  if ( $x1 === ' ' ) {
1711  if ( $firstspace == -1 ) {
1712  $firstspace = $i;
1713  }
1714  } elseif ( $x2 === ' ' ) {
1715  $firstsingleletterword = $i;
1716  // if $firstsingleletterword is set, we don't
1717  // look at the other options, so we can bail early.
1718  break;
1719  } else {
1720  if ( $firstmultiletterword == -1 ) {
1721  $firstmultiletterword = $i;
1722  }
1723  }
1724  }
1725  }
1726 
1727  // If there is a single-letter word, use it!
1728  if ( $firstsingleletterword > -1 ) {
1729  $arr[$firstsingleletterword] = "''";
1730  $arr[$firstsingleletterword - 1] .= "'";
1731  } elseif ( $firstmultiletterword > -1 ) {
1732  // If not, but there's a multi-letter word, use that one.
1733  $arr[$firstmultiletterword] = "''";
1734  $arr[$firstmultiletterword - 1] .= "'";
1735  } elseif ( $firstspace > -1 ) {
1736  // ... otherwise use the first one that has neither.
1737  // (notice that it is possible for all three to be -1 if, for example,
1738  // there is only one pentuple-apostrophe in the line)
1739  $arr[$firstspace] = "''";
1740  $arr[$firstspace - 1] .= "'";
1741  }
1742  }
1743 
1744  // Now let's actually convert our apostrophic mush to HTML!
1745  $output = '';
1746  $buffer = '';
1747  $state = '';
1748  $i = 0;
1749  foreach ( $arr as $r ) {
1750  if ( ( $i % 2 ) == 0 ) {
1751  if ( $state === 'both' ) {
1752  $buffer .= $r;
1753  } else {
1754  $output .= $r;
1755  }
1756  } else {
1757  $thislen = strlen( $r );
1758  if ( $thislen == 2 ) {
1759  if ( $state === 'i' ) {
1760  $output .= '</i>';
1761  $state = '';
1762  } elseif ( $state === 'bi' ) {
1763  $output .= '</i>';
1764  $state = 'b';
1765  } elseif ( $state === 'ib' ) {
1766  $output .= '</b></i><b>';
1767  $state = 'b';
1768  } elseif ( $state === 'both' ) {
1769  $output .= '<b><i>' . $buffer . '</i>';
1770  $state = 'b';
1771  } else { // $state can be 'b' or ''
1772  $output .= '<i>';
1773  $state .= 'i';
1774  }
1775  } elseif ( $thislen == 3 ) {
1776  if ( $state === 'b' ) {
1777  $output .= '</b>';
1778  $state = '';
1779  } elseif ( $state === 'bi' ) {
1780  $output .= '</i></b><i>';
1781  $state = 'i';
1782  } elseif ( $state === 'ib' ) {
1783  $output .= '</b>';
1784  $state = 'i';
1785  } elseif ( $state === 'both' ) {
1786  $output .= '<i><b>' . $buffer . '</b>';
1787  $state = 'i';
1788  } else { // $state can be 'i' or ''
1789  $output .= '<b>';
1790  $state .= 'b';
1791  }
1792  } elseif ( $thislen == 5 ) {
1793  if ( $state === 'b' ) {
1794  $output .= '</b><i>';
1795  $state = 'i';
1796  } elseif ( $state === 'i' ) {
1797  $output .= '</i><b>';
1798  $state = 'b';
1799  } elseif ( $state === 'bi' ) {
1800  $output .= '</i></b>';
1801  $state = '';
1802  } elseif ( $state === 'ib' ) {
1803  $output .= '</b></i>';
1804  $state = '';
1805  } elseif ( $state === 'both' ) {
1806  $output .= '<i><b>' . $buffer . '</b></i>';
1807  $state = '';
1808  } else { // ($state == '')
1809  $buffer = '';
1810  $state = 'both';
1811  }
1812  }
1813  }
1814  $i++;
1815  }
1816  // Now close all remaining tags. Notice that the order is important.
1817  if ( $state === 'b' || $state === 'ib' ) {
1818  $output .= '</b>';
1819  }
1820  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1821  $output .= '</i>';
1822  }
1823  if ( $state === 'bi' ) {
1824  $output .= '</b>';
1825  }
1826  // There might be lonely ''''', so make sure we have a buffer
1827  if ( $state === 'both' && $buffer ) {
1828  $output .= '<b><i>' . $buffer . '</i></b>';
1829  }
1830  return $output;
1831  }
1832 
1846  public function replaceExternalLinks( $text ) {
1847  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1848  if ( $bits === false ) {
1849  throw new MWException( "PCRE needs to be compiled with "
1850  . "--enable-unicode-properties in order for MediaWiki to function" );
1851  }
1852  $s = array_shift( $bits );
1853 
1854  $i = 0;
1855  while ( $i < count( $bits ) ) {
1856  $url = $bits[$i++];
1857  $i++; // protocol
1858  $text = $bits[$i++];
1859  $trail = $bits[$i++];
1860 
1861  # The characters '<' and '>' (which were escaped by
1862  # removeHTMLtags()) should not be included in
1863  # URLs, per RFC 2396.
1864  $m2 = [];
1865  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1866  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1867  $url = substr( $url, 0, $m2[0][1] );
1868  }
1869 
1870  # If the link text is an image URL, replace it with an <img> tag
1871  # This happened by accident in the original parser, but some people used it extensively
1872  $img = $this->maybeMakeExternalImage( $text );
1873  if ( $img !== false ) {
1874  $text = $img;
1875  }
1876 
1877  $dtrail = '';
1878 
1879  # Set linktype for CSS
1880  $linktype = 'text';
1881 
1882  # No link text, e.g. [http://domain.tld/some.link]
1883  if ( $text == '' ) {
1884  # Autonumber
1885  $langObj = $this->getTargetLanguage();
1886  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1887  $linktype = 'autonumber';
1888  } else {
1889  # Have link text, e.g. [http://domain.tld/some.link text]s
1890  # Check for trail
1891  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1892  }
1893 
1894  $text = $this->getConverterLanguage()->markNoConversion( $text );
1895 
1896  $url = Sanitizer::cleanUrl( $url );
1897 
1898  # Use the encoded URL
1899  # This means that users can paste URLs directly into the text
1900  # Funny characters like ö aren't valid in URLs anyway
1901  # This was changed in August 2004
1902  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1903  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1904 
1905  # Register link in the output object.
1906  $this->mOutput->addExternalLink( $url );
1907  }
1908 
1909  return $s;
1910  }
1911 
1921  public static function getExternalLinkRel( $url = false, $title = null ) {
1923  $ns = $title ? $title->getNamespace() : false;
1924  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1926  ) {
1927  return 'nofollow';
1928  }
1929  return null;
1930  }
1931 
1942  public function getExternalLinkAttribs( $url ) {
1943  $attribs = [];
1944  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1945 
1946  $target = $this->mOptions->getExternalLinkTarget();
1947  if ( $target ) {
1948  $attribs['target'] = $target;
1949  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1950  // T133507. New windows can navigate parent cross-origin.
1951  // Including noreferrer due to lacking browser
1952  // support of noopener. Eventually noreferrer should be removed.
1953  if ( $rel !== '' ) {
1954  $rel .= ' ';
1955  }
1956  $rel .= 'noreferrer noopener';
1957  }
1958  }
1959  $attribs['rel'] = $rel;
1960  return $attribs;
1961  }
1962 
1972  public static function normalizeLinkUrl( $url ) {
1973  # First, make sure unsafe characters are encoded
1974  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1975  function ( $m ) {
1976  return rawurlencode( $m[0] );
1977  },
1978  $url
1979  );
1980 
1981  $ret = '';
1982  $end = strlen( $url );
1983 
1984  # Fragment part - 'fragment'
1985  $start = strpos( $url, '#' );
1986  if ( $start !== false && $start < $end ) {
1987  $ret = self::normalizeUrlComponent(
1988  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1989  $end = $start;
1990  }
1991 
1992  # Query part - 'query' minus &=+;
1993  $start = strpos( $url, '?' );
1994  if ( $start !== false && $start < $end ) {
1995  $ret = self::normalizeUrlComponent(
1996  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
1997  $end = $start;
1998  }
1999 
2000  # Scheme and path part - 'pchar'
2001  # (we assume no userinfo or encoded colons in the host)
2002  $ret = self::normalizeUrlComponent(
2003  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2004 
2005  return $ret;
2006  }
2007 
2008  private static function normalizeUrlComponent( $component, $unsafe ) {
2009  $callback = function ( $matches ) use ( $unsafe ) {
2010  $char = urldecode( $matches[0] );
2011  $ord = ord( $char );
2012  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2013  # Unescape it
2014  return $char;
2015  } else {
2016  # Leave it escaped, but use uppercase for a-f
2017  return strtoupper( $matches[0] );
2018  }
2019  };
2020  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2021  }
2022 
2031  private function maybeMakeExternalImage( $url ) {
2032  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2033  $imagesexception = !empty( $imagesfrom );
2034  $text = false;
2035  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2036  if ( $imagesexception && is_array( $imagesfrom ) ) {
2037  $imagematch = false;
2038  foreach ( $imagesfrom as $match ) {
2039  if ( strpos( $url, $match ) === 0 ) {
2040  $imagematch = true;
2041  break;
2042  }
2043  }
2044  } elseif ( $imagesexception ) {
2045  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2046  } else {
2047  $imagematch = false;
2048  }
2049 
2050  if ( $this->mOptions->getAllowExternalImages()
2051  || ( $imagesexception && $imagematch )
2052  ) {
2053  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2054  # Image found
2055  $text = Linker::makeExternalImage( $url );
2056  }
2057  }
2058  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2059  && preg_match( self::EXT_IMAGE_REGEX, $url )
2060  ) {
2061  $whitelist = explode(
2062  "\n",
2063  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2064  );
2065 
2066  foreach ( $whitelist as $entry ) {
2067  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2068  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2069  continue;
2070  }
2071  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2072  # Image matches a whitelist entry
2073  $text = Linker::makeExternalImage( $url );
2074  break;
2075  }
2076  }
2077  }
2078  return $text;
2079  }
2080 
2090  public function replaceInternalLinks( $s ) {
2091  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2092  return $s;
2093  }
2094 
2103  public function replaceInternalLinks2( &$s ) {
2105 
2106  static $tc = false, $e1, $e1_img;
2107  # the % is needed to support urlencoded titles as well
2108  if ( !$tc ) {
2109  $tc = Title::legalChars() . '#%';
2110  # Match a link having the form [[namespace:link|alternate]]trail
2111  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2112  # Match cases where there is no "]]", which might still be images
2113  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2114  }
2115 
2116  $holders = new LinkHolderArray( $this );
2117 
2118  # split the entire text string on occurrences of [[
2119  $a = StringUtils::explode( '[[', ' ' . $s );
2120  # get the first element (all text up to first [[), and remove the space we added
2121  $s = $a->current();
2122  $a->next();
2123  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2124  $s = substr( $s, 1 );
2125 
2126  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2127  $e2 = null;
2128  if ( $useLinkPrefixExtension ) {
2129  # Match the end of a line for a word that's not followed by whitespace,
2130  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2132  $charset = $wgContLang->linkPrefixCharset();
2133  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2134  }
2135 
2136  if ( is_null( $this->mTitle ) ) {
2137  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2138  }
2139  $nottalk = !$this->mTitle->isTalkPage();
2140 
2141  if ( $useLinkPrefixExtension ) {
2142  $m = [];
2143  if ( preg_match( $e2, $s, $m ) ) {
2144  $first_prefix = $m[2];
2145  } else {
2146  $first_prefix = false;
2147  }
2148  } else {
2149  $prefix = '';
2150  }
2151 
2152  $useSubpages = $this->areSubpagesAllowed();
2153 
2154  # Loop for each link
2155  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2156  # Check for excessive memory usage
2157  if ( $holders->isBig() ) {
2158  # Too big
2159  # Do the existence check, replace the link holders and clear the array
2160  $holders->replace( $s );
2161  $holders->clear();
2162  }
2163 
2164  if ( $useLinkPrefixExtension ) {
2165  if ( preg_match( $e2, $s, $m ) ) {
2166  $prefix = $m[2];
2167  $s = $m[1];
2168  } else {
2169  $prefix = '';
2170  }
2171  # first link
2172  if ( $first_prefix ) {
2173  $prefix = $first_prefix;
2174  $first_prefix = false;
2175  }
2176  }
2177 
2178  $might_be_img = false;
2179 
2180  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2181  $text = $m[2];
2182  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2183  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2184  # the real problem is with the $e1 regex
2185  # See T1500.
2186  # Still some problems for cases where the ] is meant to be outside punctuation,
2187  # and no image is in sight. See T4095.
2188  if ( $text !== ''
2189  && substr( $m[3], 0, 1 ) === ']'
2190  && strpos( $text, '[' ) !== false
2191  ) {
2192  $text .= ']'; # so that replaceExternalLinks($text) works later
2193  $m[3] = substr( $m[3], 1 );
2194  }
2195  # fix up urlencoded title texts
2196  if ( strpos( $m[1], '%' ) !== false ) {
2197  # Should anchors '#' also be rejected?
2198  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2199  }
2200  $trail = $m[3];
2201  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2202  # Invalid, but might be an image with a link in its caption
2203  $might_be_img = true;
2204  $text = $m[2];
2205  if ( strpos( $m[1], '%' ) !== false ) {
2206  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2207  }
2208  $trail = "";
2209  } else { # Invalid form; output directly
2210  $s .= $prefix . '[[' . $line;
2211  continue;
2212  }
2213 
2214  $origLink = ltrim( $m[1], ' ' );
2215 
2216  # Don't allow internal links to pages containing
2217  # PROTO: where PROTO is a valid URL protocol; these
2218  # should be external links.
2219  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2220  $s .= $prefix . '[[' . $line;
2221  continue;
2222  }
2223 
2224  # Make subpage if necessary
2225  if ( $useSubpages ) {
2226  $link = $this->maybeDoSubpageLink( $origLink, $text );
2227  } else {
2228  $link = $origLink;
2229  }
2230 
2231  // \x7f isn't a default legal title char, so most likely strip
2232  // markers will force us into the "invalid form" path above. But,
2233  // just in case, let's assert that xmlish tags aren't valid in
2234  // the title position.
2235  $unstrip = $this->mStripState->killMarkers( $link );
2236  $noMarkers = ( $unstrip === $link );
2237 
2238  $nt = $noMarkers ? Title::newFromText( $link ) : null;
2239  if ( $nt === null ) {
2240  $s .= $prefix . '[[' . $line;
2241  continue;
2242  }
2243 
2244  $ns = $nt->getNamespace();
2245  $iw = $nt->getInterwiki();
2246 
2247  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2248 
2249  if ( $might_be_img ) { # if this is actually an invalid link
2250  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2251  $found = false;
2252  while ( true ) {
2253  # look at the next 'line' to see if we can close it there
2254  $a->next();
2255  $next_line = $a->current();
2256  if ( $next_line === false || $next_line === null ) {
2257  break;
2258  }
2259  $m = explode( ']]', $next_line, 3 );
2260  if ( count( $m ) == 3 ) {
2261  # the first ]] closes the inner link, the second the image
2262  $found = true;
2263  $text .= "[[{$m[0]}]]{$m[1]}";
2264  $trail = $m[2];
2265  break;
2266  } elseif ( count( $m ) == 2 ) {
2267  # if there's exactly one ]] that's fine, we'll keep looking
2268  $text .= "[[{$m[0]}]]{$m[1]}";
2269  } else {
2270  # if $next_line is invalid too, we need look no further
2271  $text .= '[[' . $next_line;
2272  break;
2273  }
2274  }
2275  if ( !$found ) {
2276  # we couldn't find the end of this imageLink, so output it raw
2277  # but don't ignore what might be perfectly normal links in the text we've examined
2278  $holders->merge( $this->replaceInternalLinks2( $text ) );
2279  $s .= "{$prefix}[[$link|$text";
2280  # note: no $trail, because without an end, there *is* no trail
2281  continue;
2282  }
2283  } else { # it's not an image, so output it raw
2284  $s .= "{$prefix}[[$link|$text";
2285  # note: no $trail, because without an end, there *is* no trail
2286  continue;
2287  }
2288  }
2289 
2290  $wasblank = ( $text == '' );
2291  if ( $wasblank ) {
2292  $text = $link;
2293  if ( !$noforce ) {
2294  # Strip off leading ':'
2295  $text = substr( $text, 1 );
2296  }
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 
2323  $s = rtrim( $s . $prefix ) . $trail; # T175416
2324  continue;
2325  }
2326 
2327  if ( $ns == NS_FILE ) {
2328  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2329  if ( $wasblank ) {
2330  # if no parameters were passed, $text
2331  # becomes something like "File:Foo.png",
2332  # which we don't want to pass on to the
2333  # image generator
2334  $text = '';
2335  } else {
2336  # recursively parse links inside the image caption
2337  # actually, this will parse them in any other parameters, too,
2338  # but it might be hard to fix that, and it doesn't matter ATM
2339  $text = $this->replaceExternalLinks( $text );
2340  $holders->merge( $this->replaceInternalLinks2( $text ) );
2341  }
2342  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2343  $s .= $prefix . $this->armorLinks(
2344  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2345  continue;
2346  }
2347  } elseif ( $ns == NS_CATEGORY ) {
2351  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2352 
2353  if ( $wasblank ) {
2354  $sortkey = $this->getDefaultSort();
2355  } else {
2356  $sortkey = $text;
2357  }
2358  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2359  $sortkey = str_replace( "\n", '', $sortkey );
2360  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2361  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
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' ), true );
2529  break;
2530  case 'currentmonth1':
2531  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ), true );
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' ), true );
2544  break;
2545  case 'currentday2':
2546  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ), true );
2547  break;
2548  case 'localmonth':
2549  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ), true );
2550  break;
2551  case 'localmonth1':
2552  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ), true );
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' ), true );
2565  break;
2566  case 'localday2':
2567  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ), true );
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->canHaveTalkPage() ) {
2609  $talkPage = $this->mTitle->getTalkPage();
2610  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2611  } else {
2612  $value = '';
2613  }
2614  break;
2615  case 'talkpagenamee':
2616  if ( $this->mTitle->canHaveTalkPage() ) {
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->canHaveTalkPage()
2717  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2718  : '';
2719  break;
2720  case 'talkspacee':
2721  $value = $this->mTitle->canHaveTalkPage() ? 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  // Flags
3026 
3027  // $text has been filled
3028  $found = false;
3029  // wiki markup in $text should be escaped
3030  $nowiki = false;
3031  // $text is HTML, armour it against wikitext transformation
3032  $isHTML = false;
3033  // Force interwiki transclusion to be done in raw mode not rendered
3034  $forceRawInterwiki = false;
3035  // $text is a DOM node needing expansion in a child frame
3036  $isChildObj = false;
3037  // $text is a DOM node needing expansion in the current frame
3038  $isLocalObj = false;
3039 
3040  # Title object, where $text came from
3041  $title = false;
3042 
3043  # $part1 is the bit before the first |, and must contain only title characters.
3044  # Various prefixes will be stripped from it later.
3045  $titleWithSpaces = $frame->expand( $piece['title'] );
3046  $part1 = trim( $titleWithSpaces );
3047  $titleText = false;
3048 
3049  # Original title text preserved for various purposes
3050  $originalTitle = $part1;
3051 
3052  # $args is a list of argument nodes, starting from index 0, not including $part1
3053  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3054  # below won't work b/c this $args isn't an object
3055  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3056 
3057  $profileSection = null; // profile templates
3058 
3059  # SUBST
3060  if ( !$found ) {
3061  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3062 
3063  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3064  # Decide whether to expand template or keep wikitext as-is.
3065  if ( $this->ot['wiki'] ) {
3066  if ( $substMatch === false ) {
3067  $literal = true; # literal when in PST with no prefix
3068  } else {
3069  $literal = false; # expand when in PST with subst: or safesubst:
3070  }
3071  } else {
3072  if ( $substMatch == 'subst' ) {
3073  $literal = true; # literal when not in PST with plain subst:
3074  } else {
3075  $literal = false; # expand when not in PST with safesubst: or no prefix
3076  }
3077  }
3078  if ( $literal ) {
3079  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3080  $isLocalObj = true;
3081  $found = true;
3082  }
3083  }
3084 
3085  # Variables
3086  if ( !$found && $args->getLength() == 0 ) {
3087  $id = $this->mVariables->matchStartToEnd( $part1 );
3088  if ( $id !== false ) {
3089  $text = $this->getVariableValue( $id, $frame );
3090  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3091  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3092  }
3093  $found = true;
3094  }
3095  }
3096 
3097  # MSG, MSGNW and RAW
3098  if ( !$found ) {
3099  # Check for MSGNW:
3100  $mwMsgnw = MagicWord::get( 'msgnw' );
3101  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3102  $nowiki = true;
3103  } else {
3104  # Remove obsolete MSG:
3105  $mwMsg = MagicWord::get( 'msg' );
3106  $mwMsg->matchStartAndRemove( $part1 );
3107  }
3108 
3109  # Check for RAW:
3110  $mwRaw = MagicWord::get( 'raw' );
3111  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3112  $forceRawInterwiki = true;
3113  }
3114  }
3115 
3116  # Parser functions
3117  if ( !$found ) {
3118  $colonPos = strpos( $part1, ':' );
3119  if ( $colonPos !== false ) {
3120  $func = substr( $part1, 0, $colonPos );
3121  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3122  $argsLength = $args->getLength();
3123  for ( $i = 0; $i < $argsLength; $i++ ) {
3124  $funcArgs[] = $args->item( $i );
3125  }
3126  try {
3127  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3128  } catch ( Exception $ex ) {
3129  throw $ex;
3130  }
3131 
3132  // Extract any forwarded flags
3133  if ( isset( $result['title'] ) ) {
3134  $title = $result['title'];
3135  }
3136  if ( isset( $result['found'] ) ) {
3137  $found = $result['found'];
3138  }
3139  if ( array_key_exists( 'text', $result ) ) {
3140  // a string or null
3141  $text = $result['text'];
3142  }
3143  if ( isset( $result['nowiki'] ) ) {
3144  $nowiki = $result['nowiki'];
3145  }
3146  if ( isset( $result['isHTML'] ) ) {
3147  $isHTML = $result['isHTML'];
3148  }
3149  if ( isset( $result['forceRawInterwiki'] ) ) {
3150  $forceRawInterwiki = $result['forceRawInterwiki'];
3151  }
3152  if ( isset( $result['isChildObj'] ) ) {
3153  $isChildObj = $result['isChildObj'];
3154  }
3155  if ( isset( $result['isLocalObj'] ) ) {
3156  $isLocalObj = $result['isLocalObj'];
3157  }
3158  }
3159  }
3160 
3161  # Finish mangling title and then check for loops.
3162  # Set $title to a Title object and $titleText to the PDBK
3163  if ( !$found ) {
3164  $ns = NS_TEMPLATE;
3165  # Split the title into page and subpage
3166  $subpage = '';
3167  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3168  if ( $part1 !== $relative ) {
3169  $part1 = $relative;
3170  $ns = $this->mTitle->getNamespace();
3171  }
3172  $title = Title::newFromText( $part1, $ns );
3173  if ( $title ) {
3174  $titleText = $title->getPrefixedText();
3175  # Check for language variants if the template is not found
3176  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3177  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3178  }
3179  # Do recursion depth check
3180  $limit = $this->mOptions->getMaxTemplateDepth();
3181  if ( $frame->depth >= $limit ) {
3182  $found = true;
3183  $text = '<span class="error">'
3184  . wfMessage( 'parser-template-recursion-depth-warning' )
3185  ->numParams( $limit )->inContentLanguage()->text()
3186  . '</span>';
3187  }
3188  }
3189  }
3190 
3191  # Load from database
3192  if ( !$found && $title ) {
3193  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3194  if ( !$title->isExternal() ) {
3195  if ( $title->isSpecialPage()
3196  && $this->mOptions->getAllowSpecialInclusion()
3197  && $this->ot['html']
3198  ) {
3199  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3200  // Pass the template arguments as URL parameters.
3201  // "uselang" will have no effect since the Language object
3202  // is forced to the one defined in ParserOptions.
3203  $pageArgs = [];
3204  $argsLength = $args->getLength();
3205  for ( $i = 0; $i < $argsLength; $i++ ) {
3206  $bits = $args->item( $i )->splitArg();
3207  if ( strval( $bits['index'] ) === '' ) {
3208  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3209  $value = trim( $frame->expand( $bits['value'] ) );
3210  $pageArgs[$name] = $value;
3211  }
3212  }
3213 
3214  // Create a new context to execute the special page
3215  $context = new RequestContext;
3216  $context->setTitle( $title );
3217  $context->setRequest( new FauxRequest( $pageArgs ) );
3218  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3219  $context->setUser( $this->getUser() );
3220  } else {
3221  // If this page is cached, then we better not be per user.
3222  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3223  }
3224  $context->setLanguage( $this->mOptions->getUserLangObj() );
3226  $title, $context, $this->getLinkRenderer() );
3227  if ( $ret ) {
3228  $text = $context->getOutput()->getHTML();
3229  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3230  $found = true;
3231  $isHTML = true;
3232  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3233  $this->mOutput->updateRuntimeAdaptiveExpiry(
3234  $specialPage->maxIncludeCacheTime()
3235  );
3236  }
3237  }
3238  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3239  $found = false; # access denied
3240  wfDebug( __METHOD__ . ": template inclusion denied for " .
3241  $title->getPrefixedDBkey() . "\n" );
3242  } else {
3243  list( $text, $title ) = $this->getTemplateDom( $title );
3244  if ( $text !== false ) {
3245  $found = true;
3246  $isChildObj = true;
3247  }
3248  }
3249 
3250  # If the title is valid but undisplayable, make a link to it
3251  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3252  $text = "[[:$titleText]]";
3253  $found = true;
3254  }
3255  } elseif ( $title->isTrans() ) {
3256  # Interwiki transclusion
3257  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3258  $text = $this->interwikiTransclude( $title, 'render' );
3259  $isHTML = true;
3260  } else {
3261  $text = $this->interwikiTransclude( $title, 'raw' );
3262  # Preprocess it like a template
3263  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3264  $isChildObj = true;
3265  }
3266  $found = true;
3267  }
3268 
3269  # Do infinite loop check
3270  # This has to be done after redirect resolution to avoid infinite loops via redirects
3271  if ( !$frame->loopCheck( $title ) ) {
3272  $found = true;
3273  $text = '<span class="error">'
3274  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3275  . '</span>';
3276  $this->addTrackingCategory( 'template-loop-category' );
3277  $this->mOutput->addWarning( wfMessage( 'template-loop-warning',
3278  wfEscapeWikiText( $titleText ) )->text() );
3279  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3280  }
3281  }
3282 
3283  # If we haven't found text to substitute by now, we're done
3284  # Recover the source wikitext and return it
3285  if ( !$found ) {
3286  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3287  if ( $profileSection ) {
3288  $this->mProfiler->scopedProfileOut( $profileSection );
3289  }
3290  return [ 'object' => $text ];
3291  }
3292 
3293  # Expand DOM-style return values in a child frame
3294  if ( $isChildObj ) {
3295  # Clean up argument array
3296  $newFrame = $frame->newChild( $args, $title );
3297 
3298  if ( $nowiki ) {
3299  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3300  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3301  # Expansion is eligible for the empty-frame cache
3302  $text = $newFrame->cachedExpand( $titleText, $text );
3303  } else {
3304  # Uncached expansion
3305  $text = $newFrame->expand( $text );
3306  }
3307  }
3308  if ( $isLocalObj && $nowiki ) {
3309  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3310  $isLocalObj = false;
3311  }
3312 
3313  if ( $profileSection ) {
3314  $this->mProfiler->scopedProfileOut( $profileSection );
3315  }
3316 
3317  # Replace raw HTML by a placeholder
3318  if ( $isHTML ) {
3319  $text = $this->insertStripItem( $text );
3320  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3321  # Escape nowiki-style return values
3322  $text = wfEscapeWikiText( $text );
3323  } elseif ( is_string( $text )
3324  && !$piece['lineStart']
3325  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3326  ) {
3327  # T2529: if the template begins with a table or block-level
3328  # element, it should be treated as beginning a new line.
3329  # This behavior is somewhat controversial.
3330  $text = "\n" . $text;
3331  }
3332 
3333  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3334  # Error, oversize inclusion
3335  if ( $titleText !== false ) {
3336  # Make a working, properly escaped link if possible (T25588)
3337  $text = "[[:$titleText]]";
3338  } else {
3339  # This will probably not be a working link, but at least it may
3340  # provide some hint of where the problem is
3341  preg_replace( '/^:/', '', $originalTitle );
3342  $text = "[[:$originalTitle]]";
3343  }
3344  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3345  . 'post-expand include size too large -->' );
3346  $this->limitationWarn( 'post-expand-template-inclusion' );
3347  }
3348 
3349  if ( $isLocalObj ) {
3350  $ret = [ 'object' => $text ];
3351  } else {
3352  $ret = [ 'text' => $text ];
3353  }
3354 
3355  return $ret;
3356  }
3357 
3377  public function callParserFunction( $frame, $function, array $args = [] ) {
3379 
3380  # Case sensitive functions
3381  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3382  $function = $this->mFunctionSynonyms[1][$function];
3383  } else {
3384  # Case insensitive functions
3385  $function = $wgContLang->lc( $function );
3386  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3387  $function = $this->mFunctionSynonyms[0][$function];
3388  } else {
3389  return [ 'found' => false ];
3390  }
3391  }
3392 
3393  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3394 
3395  // Avoid PHP 7.1 warning from passing $this by reference
3396  $parser = $this;
3397 
3398  $allArgs = [ &$parser ];
3399  if ( $flags & self::SFH_OBJECT_ARGS ) {
3400  # Convert arguments to PPNodes and collect for appending to $allArgs
3401  $funcArgs = [];
3402  foreach ( $args as $k => $v ) {
3403  if ( $v instanceof PPNode || $k === 0 ) {
3404  $funcArgs[] = $v;
3405  } else {
3406  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3407  }
3408  }
3409 
3410  # Add a frame parameter, and pass the arguments as an array
3411  $allArgs[] = $frame;
3412  $allArgs[] = $funcArgs;
3413  } else {
3414  # Convert arguments to plain text and append to $allArgs
3415  foreach ( $args as $k => $v ) {
3416  if ( $v instanceof PPNode ) {
3417  $allArgs[] = trim( $frame->expand( $v ) );
3418  } elseif ( is_int( $k ) && $k >= 0 ) {
3419  $allArgs[] = trim( $v );
3420  } else {
3421  $allArgs[] = trim( "$k=$v" );
3422  }
3423  }
3424  }
3425 
3426  $result = call_user_func_array( $callback, $allArgs );
3427 
3428  # The interface for function hooks allows them to return a wikitext
3429  # string or an array containing the string and any flags. This mungs
3430  # things around to match what this method should return.
3431  if ( !is_array( $result ) ) {
3432  $result = [
3433  'found' => true,
3434  'text' => $result,
3435  ];
3436  } else {
3437  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3438  $result['text'] = $result[0];
3439  }
3440  unset( $result[0] );
3441  $result += [
3442  'found' => true,
3443  ];
3444  }
3445 
3446  $noparse = true;
3447  $preprocessFlags = 0;
3448  if ( isset( $result['noparse'] ) ) {
3449  $noparse = $result['noparse'];
3450  }
3451  if ( isset( $result['preprocessFlags'] ) ) {
3452  $preprocessFlags = $result['preprocessFlags'];
3453  }
3454 
3455  if ( !$noparse ) {
3456  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3457  $result['isChildObj'] = true;
3458  }
3459 
3460  return $result;
3461  }
3462 
3471  public function getTemplateDom( $title ) {
3472  $cacheTitle = $title;
3473  $titleText = $title->getPrefixedDBkey();
3474 
3475  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3476  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3477  $title = Title::makeTitle( $ns, $dbk );
3478  $titleText = $title->getPrefixedDBkey();
3479  }
3480  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3481  return [ $this->mTplDomCache[$titleText], $title ];
3482  }
3483 
3484  # Cache miss, go to the database
3485  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3486 
3487  if ( $text === false ) {
3488  $this->mTplDomCache[$titleText] = false;
3489  return [ false, $title ];
3490  }
3491 
3492  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3493  $this->mTplDomCache[$titleText] = $dom;
3494 
3495  if ( !$title->equals( $cacheTitle ) ) {
3496  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3497  [ $title->getNamespace(), $title->getDBkey() ];
3498  }
3499 
3500  return [ $dom, $title ];
3501  }
3502 
3514  public function fetchCurrentRevisionOfTitle( $title ) {
3515  $cacheKey = $title->getPrefixedDBkey();
3516  if ( !$this->currentRevisionCache ) {
3517  $this->currentRevisionCache = new MapCacheLRU( 100 );
3518  }
3519  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3520  $this->currentRevisionCache->set( $cacheKey,
3521  // Defaults to Parser::statelessFetchRevision()
3522  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3523  );
3524  }
3525  return $this->currentRevisionCache->get( $cacheKey );
3526  }
3527 
3537  public static function statelessFetchRevision( Title $title, $parser = false ) {
3539 
3540  return $rev;
3541  }
3542 
3548  public function fetchTemplateAndTitle( $title ) {
3549  // Defaults to Parser::statelessFetchTemplate()
3550  $templateCb = $this->mOptions->getTemplateCallback();
3551  $stuff = call_user_func( $templateCb, $title, $this );
3552  // We use U+007F DELETE to distinguish strip markers from regular text.
3553  $text = $stuff['text'];
3554  if ( is_string( $stuff['text'] ) ) {
3555  $text = strtr( $text, "\x7f", "?" );
3556  }
3557  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3558  if ( isset( $stuff['deps'] ) ) {
3559  foreach ( $stuff['deps'] as $dep ) {
3560  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3561  if ( $dep['title']->equals( $this->getTitle() ) ) {
3562  // If we transclude ourselves, the final result
3563  // will change based on the new version of the page
3564  $this->mOutput->setFlag( 'vary-revision' );
3565  }
3566  }
3567  }
3568  return [ $text, $finalTitle ];
3569  }
3570 
3576  public function fetchTemplate( $title ) {
3577  return $this->fetchTemplateAndTitle( $title )[0];
3578  }
3579 
3589  public static function statelessFetchTemplate( $title, $parser = false ) {
3590  $text = $skip = false;
3591  $finalTitle = $title;
3592  $deps = [];
3593 
3594  # Loop to fetch the article, with up to 1 redirect
3595  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall
3596  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3597  # Give extensions a chance to select the revision instead
3598  $id = false; # Assume current
3599  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3600  [ $parser, $title, &$skip, &$id ] );
3601 
3602  if ( $skip ) {
3603  $text = false;
3604  $deps[] = [
3605  'title' => $title,
3606  'page_id' => $title->getArticleID(),
3607  'rev_id' => null
3608  ];
3609  break;
3610  }
3611  # Get the revision
3612  if ( $id ) {
3613  $rev = Revision::newFromId( $id );
3614  } elseif ( $parser ) {
3615  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3616  } else {
3618  }
3619  $rev_id = $rev ? $rev->getId() : 0;
3620  # If there is no current revision, there is no page
3621  if ( $id === false && !$rev ) {
3622  $linkCache = LinkCache::singleton();
3623  $linkCache->addBadLinkObj( $title );
3624  }
3625 
3626  $deps[] = [
3627  'title' => $title,
3628  'page_id' => $title->getArticleID(),
3629  'rev_id' => $rev_id ];
3630  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3631  # We fetched a rev from a different title; register it too...
3632  $deps[] = [
3633  'title' => $rev->getTitle(),
3634  'page_id' => $rev->getPage(),
3635  'rev_id' => $rev_id ];
3636  }
3637 
3638  if ( $rev ) {
3639  $content = $rev->getContent();
3640  $text = $content ? $content->getWikitextForTransclusion() : null;
3641 
3642  Hooks::run( 'ParserFetchTemplate',
3643  [ $parser, $title, $rev, &$text, &$deps ] );
3644 
3645  if ( $text === false || $text === null ) {
3646  $text = false;
3647  break;
3648  }
3649  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3651  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3652  if ( !$message->exists() ) {
3653  $text = false;
3654  break;
3655  }
3656  $content = $message->content();
3657  $text = $message->plain();
3658  } else {
3659  break;
3660  }
3661  if ( !$content ) {
3662  break;
3663  }
3664  # Redirect?
3665  $finalTitle = $title;
3666  $title = $content->getRedirectTarget();
3667  }
3668  return [
3669  'text' => $text,
3670  'finalTitle' => $finalTitle,
3671  'deps' => $deps ];
3672  }
3673 
3681  public function fetchFile( $title, $options = [] ) {
3682  return $this->fetchFileAndTitle( $title, $options )[0];
3683  }
3684 
3692  public function fetchFileAndTitle( $title, $options = [] ) {
3693  $file = $this->fetchFileNoRegister( $title, $options );
3694 
3695  $time = $file ? $file->getTimestamp() : false;
3696  $sha1 = $file ? $file->getSha1() : false;
3697  # Register the file as a dependency...
3698  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3699  if ( $file && !$title->equals( $file->getTitle() ) ) {
3700  # Update fetched file title
3701  $title = $file->getTitle();
3702  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3703  }
3704  return [ $file, $title ];
3705  }
3706 
3717  protected function fetchFileNoRegister( $title, $options = [] ) {
3718  if ( isset( $options['broken'] ) ) {
3719  $file = false; // broken thumbnail forced by hook
3720  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3721  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3722  } else { // get by (name,timestamp)
3723  $file = wfFindFile( $title, $options );
3724  }
3725  return $file;
3726  }
3727 
3736  public function interwikiTransclude( $title, $action ) {
3738 
3739  if ( !$wgEnableScaryTranscluding ) {
3740  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3741  }
3742 
3743  $url = $title->getFullURL( [ 'action' => $action ] );
3744 
3745  if ( strlen( $url ) > 255 ) {
3746  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3747  }
3748  return $this->fetchScaryTemplateMaybeFromCache( $url );
3749  }
3750 
3755  public function fetchScaryTemplateMaybeFromCache( $url ) {
3757  $dbr = wfGetDB( DB_REPLICA );
3758  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3759  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3760  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3761  if ( $obj ) {
3762  return $obj->tc_contents;
3763  }
3764 
3765  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3766  $status = $req->execute(); // Status object
3767  if ( $status->isOK() ) {
3768  $text = $req->getContent();
3769  } elseif ( $req->getStatus() != 200 ) {
3770  // Though we failed to fetch the content, this status is useless.
3771  return wfMessage( 'scarytranscludefailed-httpstatus' )
3772  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3773  } else {
3774  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3775  }
3776 
3777  $dbw = wfGetDB( DB_MASTER );
3778  $dbw->replace( 'transcache', [ 'tc_url' ], [
3779  'tc_url' => $url,
3780  'tc_time' => $dbw->timestamp( time() ),
3781  'tc_contents' => $text
3782  ] );
3783  return $text;
3784  }
3785 
3795  public function argSubstitution( $piece, $frame ) {
3796  $error = false;
3797  $parts = $piece['parts'];
3798  $nameWithSpaces = $frame->expand( $piece['title'] );
3799  $argName = trim( $nameWithSpaces );
3800  $object = false;
3801  $text = $frame->getArgument( $argName );
3802  if ( $text === false && $parts->getLength() > 0
3803  && ( $this->ot['html']
3804  || $this->ot['pre']
3805  || ( $this->ot['wiki'] && $frame->isTemplate() )
3806  )
3807  ) {
3808  # No match in frame, use the supplied default
3809  $object = $parts->item( 0 )->getChildren();
3810  }
3811  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3812  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3813  $this->limitationWarn( 'post-expand-template-argument' );
3814  }
3815 
3816  if ( $text === false && $object === false ) {
3817  # No match anywhere
3818  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3819  }
3820  if ( $error !== false ) {
3821  $text .= $error;
3822  }
3823  if ( $object !== false ) {
3824  $ret = [ 'object' => $object ];
3825  } else {
3826  $ret = [ 'text' => $text ];
3827  }
3828 
3829  return $ret;
3830  }
3831 
3847  public function extensionSubstitution( $params, $frame ) {
3848  static $errorStr = '<span class="error">';
3849  static $errorLen = 20;
3850 
3851  $name = $frame->expand( $params['name'] );
3852  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3853  // Probably expansion depth or node count exceeded. Just punt the
3854  // error up.
3855  return $name;
3856  }
3857 
3858  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3859  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3860  // See above
3861  return $attrText;
3862  }
3863 
3864  // We can't safely check if the expansion for $content resulted in an
3865  // error, because the content could happen to be the error string
3866  // (T149622).
3867  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3868 
3869  $marker = self::MARKER_PREFIX . "-$name-"
3870  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3871 
3872  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3873  ( $this->ot['html'] || $this->ot['pre'] );
3874  if ( $isFunctionTag ) {
3875  $markerType = 'none';
3876  } else {
3877  $markerType = 'general';
3878  }
3879  if ( $this->ot['html'] || $isFunctionTag ) {
3880  $name = strtolower( $name );
3881  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3882  if ( isset( $params['attributes'] ) ) {
3883  $attributes = $attributes + $params['attributes'];
3884  }
3885 
3886  if ( isset( $this->mTagHooks[$name] ) ) {
3887  $output = call_user_func_array( $this->mTagHooks[$name],
3888  [ $content, $attributes, $this, $frame ] );
3889  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3890  list( $callback, ) = $this->mFunctionTagHooks[$name];
3891 
3892  // Avoid PHP 7.1 warning from passing $this by reference
3893  $parser = $this;
3894  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3895  } else {
3896  $output = '<span class="error">Invalid tag extension name: ' .
3897  htmlspecialchars( $name ) . '</span>';
3898  }
3899 
3900  if ( is_array( $output ) ) {
3901  // Extract flags
3902  $flags = $output;
3903  $output = $flags[0];
3904  if ( isset( $flags['markerType'] ) ) {
3905  $markerType = $flags['markerType'];
3906  }
3907  }
3908  } else {
3909  if ( is_null( $attrText ) ) {
3910  $attrText = '';
3911  }
3912  if ( isset( $params['attributes'] ) ) {
3913  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3914  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3915  htmlspecialchars( $attrValue ) . '"';
3916  }
3917  }
3918  if ( $content === null ) {
3919  $output = "<$name$attrText/>";
3920  } else {
3921  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3922  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3923  // See above
3924  return $close;
3925  }
3926  $output = "<$name$attrText>$content$close";
3927  }
3928  }
3929 
3930  if ( $markerType === 'none' ) {
3931  return $output;
3932  } elseif ( $markerType === 'nowiki' ) {
3933  $this->mStripState->addNoWiki( $marker, $output );
3934  } elseif ( $markerType === 'general' ) {
3935  $this->mStripState->addGeneral( $marker, $output );
3936  } else {
3937  throw new MWException( __METHOD__ . ': invalid marker type' );
3938  }
3939  return $marker;
3940  }
3941 
3949  public function incrementIncludeSize( $type, $size ) {
3950  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3951  return false;
3952  } else {
3953  $this->mIncludeSizes[$type] += $size;
3954  return true;
3955  }
3956  }
3957 
3963  public function incrementExpensiveFunctionCount() {
3964  $this->mExpensiveFunctionCount++;
3965  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3966  }
3967 
3976  public function doDoubleUnderscore( $text ) {
3977  # The position of __TOC__ needs to be recorded
3978  $mw = MagicWord::get( 'toc' );
3979  if ( $mw->match( $text ) ) {
3980  $this->mShowToc = true;
3981  $this->mForceTocPosition = true;
3982 
3983  # Set a placeholder. At the end we'll fill it in with the TOC.
3984  $text = $mw->replace( '<!--MWTOC\'"-->', $text, 1 );
3985 
3986  # Only keep the first one.
3987  $text = $mw->replace( '', $text );
3988  }
3989 
3990  # Now match and remove the rest of them
3992  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3993 
3994  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3995  $this->mOutput->mNoGallery = true;
3996  }
3997  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3998  $this->mShowToc = false;
3999  }
4000  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4001  && $this->mTitle->getNamespace() == NS_CATEGORY
4002  ) {
4003  $this->addTrackingCategory( 'hidden-category-category' );
4004  }
4005  # (T10068) Allow control over whether robots index a page.
4006  # __INDEX__ always overrides __NOINDEX__, see T16899
4007  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4008  $this->mOutput->setIndexPolicy( 'noindex' );
4009  $this->addTrackingCategory( 'noindex-category' );
4010  }
4011  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4012  $this->mOutput->setIndexPolicy( 'index' );
4013  $this->addTrackingCategory( 'index-category' );
4014  }
4015 
4016  # Cache all double underscores in the database
4017  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4018  $this->mOutput->setProperty( $key, '' );
4019  }
4020 
4021  return $text;
4022  }
4023 
4029  public function addTrackingCategory( $msg ) {
4030  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4031  }
4032 
4049  public function formatHeadings( $text, $origText, $isMain = true ) {
4051 
4052  # Inhibit editsection links if requested in the page
4053  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4054  $maybeShowEditLink = false;
4055  } else {
4056  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4057  }
4058 
4059  # Get all headlines for numbering them and adding funky stuff like [edit]
4060  # links - this is for later, but we need the number of headlines right now
4061  # NOTE: white space in headings have been trimmed in doHeadings. They shouldn't
4062  # be trimmed here since whitespace in HTML headings is significant.
4063  $matches = [];
4064  $numMatches = preg_match_all(
4065  '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4066  $text,
4067  $matches
4068  );
4069 
4070  # if there are fewer than 4 headlines in the article, do not show TOC
4071  # unless it's been explicitly enabled.
4072  $enoughToc = $this->mShowToc &&
4073  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4074 
4075  # Allow user to stipulate that a page should have a "new section"
4076  # link added via __NEWSECTIONLINK__
4077  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4078  $this->mOutput->setNewSection( true );
4079  }
4080 
4081  # Allow user to remove the "new section"
4082  # link via __NONEWSECTIONLINK__
4083  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4084  $this->mOutput->hideNewSection( true );
4085  }
4086 
4087  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4088  # override above conditions and always show TOC above first header
4089  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4090  $this->mShowToc = true;
4091  $enoughToc = true;
4092  }
4093 
4094  # headline counter
4095  $headlineCount = 0;
4096  $numVisible = 0;
4097 
4098  # Ugh .. the TOC should have neat indentation levels which can be
4099  # passed to the skin functions. These are determined here
4100  $toc = '';
4101  $full = '';
4102  $head = [];
4103  $sublevelCount = [];
4104  $levelCount = [];
4105  $level = 0;
4106  $prevlevel = 0;
4107  $toclevel = 0;
4108  $prevtoclevel = 0;
4109  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4110  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4111  $oldType = $this->mOutputType;
4112  $this->setOutputType( self::OT_WIKI );
4113  $frame = $this->getPreprocessor()->newFrame();
4114  $root = $this->preprocessToDom( $origText );
4115  $node = $root->getFirstChild();
4116  $byteOffset = 0;
4117  $tocraw = [];
4118  $refers = [];
4119 
4120  $headlines = $numMatches !== false ? $matches[3] : [];
4121 
4122  foreach ( $headlines as $headline ) {
4123  $isTemplate = false;
4124  $titleText = false;
4125  $sectionIndex = false;
4126  $numbering = '';
4127  $markerMatches = [];
4128  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4129  $serial = $markerMatches[1];
4130  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4131  $isTemplate = ( $titleText != $baseTitleText );
4132  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4133  }
4134 
4135  if ( $toclevel ) {
4136  $prevlevel = $level;
4137  }
4138  $level = $matches[1][$headlineCount];
4139 
4140  if ( $level > $prevlevel ) {
4141  # Increase TOC level
4142  $toclevel++;
4143  $sublevelCount[$toclevel] = 0;
4144  if ( $toclevel < $wgMaxTocLevel ) {
4145  $prevtoclevel = $toclevel;
4146  $toc .= Linker::tocIndent();
4147  $numVisible++;
4148  }
4149  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4150  # Decrease TOC level, find level to jump to
4151 
4152  for ( $i = $toclevel; $i > 0; $i-- ) {
4153  if ( $levelCount[$i] == $level ) {
4154  # Found last matching level
4155  $toclevel = $i;
4156  break;
4157  } elseif ( $levelCount[$i] < $level ) {
4158  # Found first matching level below current level
4159  $toclevel = $i + 1;
4160  break;
4161  }
4162  }
4163  if ( $i == 0 ) {
4164  $toclevel = 1;
4165  }
4166  if ( $toclevel < $wgMaxTocLevel ) {
4167  if ( $prevtoclevel < $wgMaxTocLevel ) {
4168  # Unindent only if the previous toc level was shown :p
4169  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4170  $prevtoclevel = $toclevel;
4171  } else {
4172  $toc .= Linker::tocLineEnd();
4173  }
4174  }
4175  } else {
4176  # No change in level, end TOC line
4177  if ( $toclevel < $wgMaxTocLevel ) {
4178  $toc .= Linker::tocLineEnd();
4179  }
4180  }
4181 
4182  $levelCount[$toclevel] = $level;
4183 
4184  # count number of headlines for each level
4185  $sublevelCount[$toclevel]++;
4186  $dot = 0;
4187  for ( $i = 1; $i <= $toclevel; $i++ ) {
4188  if ( !empty( $sublevelCount[$i] ) ) {
4189  if ( $dot ) {
4190  $numbering .= '.';
4191  }
4192  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4193  $dot = 1;
4194  }
4195  }
4196 
4197  # The safe header is a version of the header text safe to use for links
4198 
4199  # Remove link placeholders by the link text.
4200  # <!--LINK number-->
4201  # turns into
4202  # link text with suffix
4203  # Do this before unstrip since link text can contain strip markers
4204  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4205 
4206  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4207  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4208 
4209  # Strip out HTML (first regex removes any tag not allowed)
4210  # Allowed tags are:
4211  # * <sup> and <sub> (T10393)
4212  # * <i> (T28375)
4213  # * <b> (r105284)
4214  # * <bdi> (T74884)
4215  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4216  # * <s> and <strike> (T35715)
4217  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4218  # to allow setting directionality in toc items.
4219  $tocline = preg_replace(
4220  [
4221  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4222  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4223  ],
4224  [ '', '<$1>' ],
4225  $safeHeadline
4226  );
4227 
4228  # Strip '<span></span>', which is the result from the above if
4229  # <span id="foo"></span> is used to produce an additional anchor
4230  # for a section.
4231  $tocline = str_replace( '<span></span>', '', $tocline );
4232 
4233  $tocline = trim( $tocline );
4234 
4235  # For the anchor, strip out HTML-y stuff period
4236  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4237  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4238 
4239  # Save headline for section edit hint before it's escaped
4240  $headlineHint = $safeHeadline;
4241 
4242  # Decode HTML entities
4243  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4244 
4245  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4246 
4247  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4248  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4249  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4250  if ( $fallbackHeadline === $safeHeadline ) {
4251  # No reason to have both (in fact, we can't)
4252  $fallbackHeadline = false;
4253  }
4254 
4255  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4256  # @todo FIXME: We may be changing them depending on the current locale.
4257  $arrayKey = strtolower( $safeHeadline );
4258  if ( $fallbackHeadline === false ) {
4259  $fallbackArrayKey = false;
4260  } else {
4261  $fallbackArrayKey = strtolower( $fallbackHeadline );
4262  }
4263 
4264  # Create the anchor for linking from the TOC to the section
4265  $anchor = $safeHeadline;
4266  $fallbackAnchor = $fallbackHeadline;
4267  if ( isset( $refers[$arrayKey] ) ) {
4268  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall,Generic.Formatting.DisallowMultipleStatements
4269  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4270  $anchor .= "_$i";
4271  $linkAnchor .= "_$i";
4272  $refers["${arrayKey}_$i"] = true;
4273  } else {
4274  $refers[$arrayKey] = true;
4275  }
4276  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4277  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall,Generic.Formatting.DisallowMultipleStatements
4278  for ( $i = 2; isset( $refers["${fallbackArrayKey}_$i"] ); ++$i );
4279  $fallbackAnchor .= "_$i";
4280  $refers["${fallbackArrayKey}_$i"] = true;
4281  } else {
4282  $refers[$fallbackArrayKey] = true;
4283  }
4284 
4285  # Don't number the heading if it is the only one (looks silly)
4286  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4287  # the two are different if the line contains a link
4288  $headline = Html::element(
4289  'span',
4290  [ 'class' => 'mw-headline-number' ],
4291  $numbering
4292  ) . ' ' . $headline;
4293  }
4294 
4295  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4296  $toc .= Linker::tocLine( $linkAnchor, $tocline,
4297  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4298  }
4299 
4300  # Add the section to the section tree
4301  # Find the DOM node for this header
4302  $noOffset = ( $isTemplate || $sectionIndex === false );
4303  while ( $node && !$noOffset ) {
4304  if ( $node->getName() === 'h' ) {
4305  $bits = $node->splitHeading();
4306  if ( $bits['i'] == $sectionIndex ) {
4307  break;
4308  }
4309  }
4310  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4311  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4312  $node = $node->getNextSibling();
4313  }
4314  $tocraw[] = [
4315  'toclevel' => $toclevel,
4316  'level' => $level,
4317  'line' => $tocline,
4318  'number' => $numbering,
4319  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4320  'fromtitle' => $titleText,
4321  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4322  'anchor' => $anchor,
4323  ];
4324 
4325  # give headline the correct <h#> tag
4326  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4327  // Output edit section links as markers with styles that can be customized by skins
4328  if ( $isTemplate ) {
4329  # Put a T flag in the section identifier, to indicate to extractSections()
4330  # that sections inside <includeonly> should be counted.
4331  $editsectionPage = $titleText;
4332  $editsectionSection = "T-$sectionIndex";
4333  $editsectionContent = null;
4334  } else {
4335  $editsectionPage = $this->mTitle->getPrefixedText();
4336  $editsectionSection = $sectionIndex;
4337  $editsectionContent = $headlineHint;
4338  }
4339  // We use a bit of pesudo-xml for editsection markers. The
4340  // language converter is run later on. Using a UNIQ style marker
4341  // leads to the converter screwing up the tokens when it
4342  // converts stuff. And trying to insert strip tags fails too. At
4343  // this point all real inputted tags have already been escaped,
4344  // so we don't have to worry about a user trying to input one of
4345  // these markers directly. We use a page and section attribute
4346  // to stop the language converter from converting these
4347  // important bits of data, but put the headline hint inside a
4348  // content block because the language converter is supposed to
4349  // be able to convert that piece of data.
4350  // Gets replaced with html in ParserOutput::getText
4351  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4352  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4353  if ( $editsectionContent !== null ) {
4354  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4355  } else {
4356  $editlink .= '/>';
4357  }
4358  } else {
4359  $editlink = '';
4360  }
4361  $head[$headlineCount] = Linker::makeHeadline( $level,
4362  $matches['attrib'][$headlineCount], $anchor, $headline,
4363  $editlink, $fallbackAnchor );
4364 
4365  $headlineCount++;
4366  }
4367 
4368  $this->setOutputType( $oldType );
4369 
4370  # Never ever show TOC if no headers
4371  if ( $numVisible < 1 ) {
4372  $enoughToc = false;
4373  }
4374 
4375  if ( $enoughToc ) {
4376  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4377  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4378  }
4379  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4380  $this->mOutput->setTOCHTML( $toc );
4381  $toc = self::TOC_START . $toc . self::TOC_END;
4382  }
4383 
4384  if ( $isMain ) {
4385  $this->mOutput->setSections( $tocraw );
4386  }
4387 
4388  # split up and insert constructed headlines
4389  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4390  $i = 0;
4391 
4392  // build an array of document sections
4393  $sections = [];
4394  foreach ( $blocks as $block ) {
4395  // $head is zero-based, sections aren't.
4396  if ( empty( $head[$i - 1] ) ) {
4397  $sections[$i] = $block;
4398  } else {
4399  $sections[$i] = $head[$i - 1] . $block;
4400  }
4401 
4412  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ] );
4413 
4414  $i++;
4415  }
4416 
4417  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4418  // append the TOC at the beginning
4419  // Top anchor now in skin
4420  $sections[0] = $sections[0] . $toc . "\n";
4421  }
4422 
4423  $full .= implode( '', $sections );
4424 
4425  if ( $this->mForceTocPosition ) {
4426  return str_replace( '<!--MWTOC\'"-->', $toc, $full );
4427  } else {
4428  return $full;
4429  }
4430  }
4431 
4443  public function preSaveTransform( $text, Title $title, User $user,
4444  ParserOptions $options, $clearState = true
4445  ) {
4446  if ( $clearState ) {
4447  $magicScopeVariable = $this->lock();
4448  }
4449  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4450  $this->setUser( $user );
4451 
4452  // Strip U+0000 NULL (T159174)
4453  $text = str_replace( "\000", '', $text );
4454 
4455  // We still normalize line endings for backwards-compatibility
4456  // with other code that just calls PST, but this should already
4457  // be handled in TextContent subclasses
4458  $text = TextContent::normalizeLineEndings( $text );
4459 
4460  if ( $options->getPreSaveTransform() ) {
4461  $text = $this->pstPass2( $text, $user );
4462  }
4463  $text = $this->mStripState->unstripBoth( $text );
4464 
4465  $this->setUser( null ); # Reset
4466 
4467  return $text;
4468  }
4469 
4478  private function pstPass2( $text, $user ) {
4480 
4481  # Note: This is the timestamp saved as hardcoded wikitext to
4482  # the database, we use $wgContLang here in order to give
4483  # everyone the same signature and use the default one rather
4484  # than the one selected in each user's preferences.
4485  # (see also T14815)
4486  $ts = $this->mOptions->getTimestamp();
4487  $timestamp = MWTimestamp::getLocalInstance( $ts );
4488  $ts = $timestamp->format( 'YmdHis' );
4489  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4490 
4491  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4492 
4493  # Variable replacement
4494  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4495  $text = $this->replaceVariables( $text );
4496 
4497  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4498  # which may corrupt this parser instance via its wfMessage()->text() call-
4499 
4500  # Signatures
4501  if ( strpos( $text, '~~~' ) !== false ) {
4502  $sigText = $this->getUserSig( $user );
4503  $text = strtr( $text, [
4504  '~~~~~' => $d,
4505  '~~~~' => "$sigText $d",
4506  '~~~' => $sigText
4507  ] );
4508  # The main two signature forms used above are time-sensitive
4509  $this->mOutput->setFlag( 'user-signature' );
4510  }
4511 
4512  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4513  $tc = '[' . Title::legalChars() . ']';
4514  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4515 
4516  // [[ns:page (context)|]]
4517  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4518  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4519  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4520  // [[ns:page (context), context|]] (using either single or double-width comma)
4521  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4522  // [[|page]] (reverse pipe trick: add context from page title)
4523  $p2 = "/\[\[\\|($tc+)]]/";
4524 
4525  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4526  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4527  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4528  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4529 
4530  $t = $this->mTitle->getText();
4531  $m = [];
4532  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4533  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4534  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4535  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4536  } else {
4537  # if there's no context, don't bother duplicating the title
4538  $text = preg_replace( $p2, '[[\\1]]', $text );
4539  }
4540 
4541  return $text;
4542  }
4543 
4558  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4560 
4561  $username = $user->getName();
4562 
4563  # If not given, retrieve from the user object.
4564  if ( $nickname === false ) {
4565  $nickname = $user->getOption( 'nickname' );
4566  }
4567 
4568  if ( is_null( $fancySig ) ) {
4569  $fancySig = $user->getBoolOption( 'fancysig' );
4570  }
4571 
4572  $nickname = $nickname == null ? $username : $nickname;
4573 
4574  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4575  $nickname = $username;
4576  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4577  } elseif ( $fancySig !== false ) {
4578  # Sig. might contain markup; validate this
4579  if ( $this->validateSig( $nickname ) !== false ) {
4580  # Validated; clean up (if needed) and return it
4581  return $this->cleanSig( $nickname, true );
4582  } else {
4583  # Failed to validate; fall back to the default
4584  $nickname = $username;
4585  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4586  }
4587  }
4588 
4589  # Make sure nickname doesnt get a sig in a sig
4590  $nickname = self::cleanSigInSig( $nickname );
4591 
4592  # If we're still here, make it a link to the user page
4593  $userText = wfEscapeWikiText( $username );
4594  $nickText = wfEscapeWikiText( $nickname );
4595  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4596 
4597  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4598  ->title( $this->getTitle() )->text();
4599  }
4600 
4607  public function validateSig( $text ) {
4608  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4609  }
4610 
4621  public function cleanSig( $text, $parsing = false ) {
4622  if ( !$parsing ) {
4623  global $wgTitle;
4624  $magicScopeVariable = $this->lock();
4625  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4626  }
4627 
4628  # Option to disable this feature
4629  if ( !$this->mOptions->getCleanSignatures() ) {
4630  return $text;
4631  }
4632 
4633  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4634  # => Move this logic to braceSubstitution()
4635  $substWord = MagicWord::get( 'subst' );
4636  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4637  $substText = '{{' . $substWord->getSynonym( 0 );
4638 
4639  $text = preg_replace( $substRegex, $substText, $text );
4640  $text = self::cleanSigInSig( $text );
4641  $dom = $this->preprocessToDom( $text );
4642  $frame = $this->getPreprocessor()->newFrame();
4643  $text = $frame->expand( $dom );
4644 
4645  if ( !$parsing ) {
4646  $text = $this->mStripState->unstripBoth( $text );
4647  }
4648 
4649  return $text;
4650  }
4651 
4658  public static function cleanSigInSig( $text ) {
4659  $text = preg_replace( '/~{3,5}/', '', $text );
4660  return $text;
4661  }
4662 
4672  public function startExternalParse( Title $title = null, ParserOptions $options,
4673  $outputType, $clearState = true
4674  ) {
4675  $this->startParse( $title, $options, $outputType, $clearState );
4676  }
4677 
4684  private function startParse( Title $title = null, ParserOptions $options,
4685  $outputType, $clearState = true
4686  ) {
4687  $this->setTitle( $title );
4688  $this->mOptions = $options;
4689  $this->setOutputType( $outputType );
4690  if ( $clearState ) {
4691  $this->clearState();
4692  }
4693  }
4694 
4703  public function transformMsg( $text, $options, $title = null ) {
4704  static $executing = false;
4705 
4706  # Guard against infinite recursion
4707  if ( $executing ) {
4708  return $text;
4709  }
4710  $executing = true;
4711 
4712  if ( !$title ) {
4713  global $wgTitle;
4714  $title = $wgTitle;
4715  }
4716 
4717  $text = $this->preprocess( $text, $title, $options );
4718 
4719  $executing = false;
4720  return $text;
4721  }
4722 
4747  public function setHook( $tag, callable $callback ) {
4748  $tag = strtolower( $tag );
4749  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4750  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4751  }
4752  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4753  $this->mTagHooks[$tag] = $callback;
4754  if ( !in_array( $tag, $this->mStripList ) ) {
4755  $this->mStripList[] = $tag;
4756  }
4757 
4758  return $oldVal;
4759  }
4760 
4778  public function setTransparentTagHook( $tag, callable $callback ) {
4779  $tag = strtolower( $tag );
4780  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4781  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4782  }
4783  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4784  $this->mTransparentTagHooks[$tag] = $callback;
4785 
4786  return $oldVal;
4787  }
4788 
4792  public function clearTagHooks() {
4793  $this->mTagHooks = [];
4794  $this->mFunctionTagHooks = [];
4795  $this->mStripList = $this->mDefaultStripList;
4796  }
4797 
4841  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4843 
4844  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4845  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4846 
4847  # Add to function cache
4848  $mw = MagicWord::get( $id );
4849  if ( !$mw ) {
4850  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4851  }
4852 
4853  $synonyms = $mw->getSynonyms();
4854  $sensitive = intval( $mw->isCaseSensitive() );
4855 
4856  foreach ( $synonyms as $syn ) {
4857  # Case
4858  if ( !$sensitive ) {
4859  $syn = $wgContLang->lc( $syn );
4860  }
4861  # Add leading hash
4862  if ( !( $flags & self::SFH_NO_HASH ) ) {
4863  $syn = '#' . $syn;
4864  }
4865  # Remove trailing colon
4866  if ( substr( $syn, -1, 1 ) === ':' ) {
4867  $syn = substr( $syn, 0, -1 );
4868  }
4869  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4870  }
4871  return $oldVal;
4872  }
4873 
4879  public function getFunctionHooks() {
4880  return array_keys( $this->mFunctionHooks );
4881  }
4882 
4893  public function setFunctionTagHook( $tag, callable $callback, $flags ) {
4894  $tag = strtolower( $tag );
4895  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4896  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4897  }
4898  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4899  $this->mFunctionTagHooks[$tag] : null;
4900  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4901 
4902  if ( !in_array( $tag, $this->mStripList ) ) {
4903  $this->mStripList[] = $tag;
4904  }
4905 
4906  return $old;
4907  }
4908 
4916  public function replaceLinkHolders( &$text, $options = 0 ) {
4917  $this->mLinkHolders->replace( $text );
4918  }
4919 
4927  public function replaceLinkHoldersText( $text ) {
4928  return $this->mLinkHolders->replaceText( $text );
4929  }
4930 
4944  public function renderImageGallery( $text, $params ) {
4945  $mode = false;
4946  if ( isset( $params['mode'] ) ) {
4947  $mode = $params['mode'];
4948  }
4949 
4950  try {
4951  $ig = ImageGalleryBase::factory( $mode );
4952  } catch ( Exception $e ) {
4953  // If invalid type set, fallback to default.
4954  $ig = ImageGalleryBase::factory( false );
4955  }
4956 
4957  $ig->setContextTitle( $this->mTitle );
4958  $ig->setShowBytes( false );
4959  $ig->setShowDimensions( false );
4960  $ig->setShowFilename( false );
4961  $ig->setParser( $this );
4962  $ig->setHideBadImages();
4963  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4964 
4965  if ( isset( $params['showfilename'] ) ) {
4966  $ig->setShowFilename( true );
4967  } else {
4968  $ig->setShowFilename( false );
4969  }
4970  if ( isset( $params['caption'] ) ) {
4971  $caption = $params['caption'];
4972  $caption = htmlspecialchars( $caption );
4973  $caption = $this->replaceInternalLinks( $caption );
4974  $ig->setCaptionHtml( $caption );
4975  }
4976  if ( isset( $params['perrow'] ) ) {
4977  $ig->setPerRow( $params['perrow'] );
4978  }
4979  if ( isset( $params['widths'] ) ) {
4980  $ig->setWidths( $params['widths'] );
4981  }
4982  if ( isset( $params['heights'] ) ) {
4983  $ig->setHeights( $params['heights'] );
4984  }
4985  $ig->setAdditionalOptions( $params );
4986 
4987  // Avoid PHP 7.1 warning from passing $this by reference
4988  $parser = $this;
4989  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
4990 
4991  $lines = StringUtils::explode( "\n", $text );
4992  foreach ( $lines as $line ) {
4993  # match lines like these:
4994  # Image:someimage.jpg|This is some image
4995  $matches = [];
4996  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4997  # Skip empty lines
4998  if ( count( $matches ) == 0 ) {
4999  continue;
5000  }
5001 
5002  if ( strpos( $matches[0], '%' ) !== false ) {
5003  $matches[1] = rawurldecode( $matches[1] );
5004  }
5006  if ( is_null( $title ) ) {
5007  # Bogus title. Ignore these so we don't bomb out later.
5008  continue;
5009  }
5010 
5011  # We need to get what handler the file uses, to figure out parameters.
5012  # Note, a hook can overide the file name, and chose an entirely different
5013  # file (which potentially could be of a different type and have different handler).
5014  $options = [];
5015  $descQuery = false;
5016  Hooks::run( 'BeforeParserFetchFileAndTitle',
5017  [ $this, $title, &$options, &$descQuery ] );
5018  # Don't register it now, as TraditionalImageGallery does that later.
5019  $file = $this->fetchFileNoRegister( $title, $options );
5020  $handler = $file ? $file->getHandler() : false;
5021 
5022  $paramMap = [
5023  'img_alt' => 'gallery-internal-alt',
5024  'img_link' => 'gallery-internal-link',
5025  ];
5026  if ( $handler ) {
5027  $paramMap = $paramMap + $handler->getParamMap();
5028  // We don't want people to specify per-image widths.
5029  // Additionally the width parameter would need special casing anyhow.
5030  unset( $paramMap['img_width'] );
5031  }
5032 
5033  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5034 
5035  $label = '';
5036  $alt = '';
5037  $link = '';
5038  $handlerOptions = [];
5039  if ( isset( $matches[3] ) ) {
5040  // look for an |alt= definition while trying not to break existing
5041  // captions with multiple pipes (|) in it, until a more sensible grammar
5042  // is defined for images in galleries
5043 
5044  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5045  // splitting on '|' is a bit odd, and different from makeImage.
5046  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5047  // Protect LanguageConverter markup
5048  $parameterMatches = StringUtils::delimiterExplode(
5049  '-{', '}-', '|', $matches[3], true /* nested */
5050  );
5051 
5052  foreach ( $parameterMatches as $parameterMatch ) {
5053  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5054  if ( $magicName ) {
5055  $paramName = $paramMap[$magicName];
5056 
5057  switch ( $paramName ) {
5058  case 'gallery-internal-alt':
5059  $alt = $this->stripAltText( $match, false );
5060  break;
5061  case 'gallery-internal-link':
5062  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5063  $chars = self::EXT_LINK_URL_CLASS;
5064  $addr = self::EXT_LINK_ADDR;
5065  $prots = $this->mUrlProtocols;
5066  // check to see if link matches an absolute url, if not then it must be a wiki link.
5067  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5068  // Result of LanguageConverter::markNoConversion
5069  // invoked on an external link.
5070  $linkValue = substr( $linkValue, 4, -2 );
5071  }
5072  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5073  $link = $linkValue;
5074  $this->mOutput->addExternalLink( $link );
5075  } else {
5076  $localLinkTitle = Title::newFromText( $linkValue );
5077  if ( $localLinkTitle !== null ) {
5078  $this->mOutput->addLink( $localLinkTitle );
5079  $link = $localLinkTitle->getLinkURL();
5080  }
5081  }
5082  break;
5083  default:
5084  // Must be a handler specific parameter.
5085  if ( $handler->validateParam( $paramName, $match ) ) {
5086  $handlerOptions[$paramName] = $match;
5087  } else {
5088  // Guess not, consider it as caption.
5089  wfDebug( "$parameterMatch failed parameter validation\n" );
5090  $label = '|' . $parameterMatch;
5091  }
5092  }
5093 
5094  } else {
5095  // Last pipe wins.
5096  $label = '|' . $parameterMatch;
5097  }
5098  }
5099  // Remove the pipe.
5100  $label = substr( $label, 1 );
5101  }
5102 
5103  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5104  }
5105  $html = $ig->toHTML();
5106  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5107  return $html;
5108  }
5109 
5114  public function getImageParams( $handler ) {
5115  if ( $handler ) {
5116  $handlerClass = get_class( $handler );
5117  } else {
5118  $handlerClass = '';
5119  }
5120  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5121  # Initialise static lists
5122  static $internalParamNames = [
5123  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5124  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5125  'bottom', 'text-bottom' ],
5126  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5127  'upright', 'border', 'link', 'alt', 'class' ],
5128  ];
5129  static $internalParamMap;
5130  if ( !$internalParamMap ) {
5131  $internalParamMap = [];
5132  foreach ( $internalParamNames as $type => $names ) {
5133  foreach ( $names as $name ) {
5134  // For grep: img_left, img_right, img_center, img_none,
5135  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5136  // img_bottom, img_text_bottom,
5137  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5138  // img_border, img_link, img_alt, img_class
5139  $magicName = str_replace( '-', '_', "img_$name" );
5140  $internalParamMap[$magicName] = [ $type, $name ];
5141  }
5142  }
5143  }
5144 
5145  # Add handler params
5146  $paramMap = $internalParamMap;
5147  if ( $handler ) {
5148  $handlerParamMap = $handler->getParamMap();
5149  foreach ( $handlerParamMap as $magic => $paramName ) {
5150  $paramMap[$magic] = [ 'handler', $paramName ];
5151  }
5152  }
5153  $this->mImageParams[$handlerClass] = $paramMap;
5154  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5155  }
5156  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5157  }
5158 
5167  public function makeImage( $title, $options, $holders = false ) {
5168  # Check if the options text is of the form "options|alt text"
5169  # Options are:
5170  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5171  # * left no resizing, just left align. label is used for alt= only
5172  # * right same, but right aligned
5173  # * none same, but not aligned
5174  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5175  # * center center the image
5176  # * frame Keep original image size, no magnify-button.
5177  # * framed Same as "frame"
5178  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5179  # * upright reduce width for upright images, rounded to full __0 px
5180  # * border draw a 1px border around the image
5181  # * alt Text for HTML alt attribute (defaults to empty)
5182  # * class Set a class for img node
5183  # * link Set the target of the image link. Can be external, interwiki, or local
5184  # vertical-align values (no % or length right now):
5185  # * baseline
5186  # * sub
5187  # * super
5188  # * top
5189  # * text-top
5190  # * middle
5191  # * bottom
5192  # * text-bottom
5193 
5194  # Protect LanguageConverter markup when splitting into parts
5196  '-{', '}-', '|', $options, true /* allow nesting */
5197  );
5198 
5199  # Give extensions a chance to select the file revision for us
5200  $options = [];
5201  $descQuery = false;
5202  Hooks::run( 'BeforeParserFetchFileAndTitle',
5203  [ $this, $title, &$options, &$descQuery ] );
5204  # Fetch and register the file (file title may be different via hooks)
5205  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5206 
5207  # Get parameter map
5208  $handler = $file ? $file->getHandler() : false;
5209 
5210  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5211 
5212  if ( !$file ) {
5213  $this->addTrackingCategory( 'broken-file-category' );
5214  }
5215 
5216  # Process the input parameters
5217  $caption = '';
5218  $params = [ 'frame' => [], 'handler' => [],
5219  'horizAlign' => [], 'vertAlign' => [] ];
5220  $seenformat = false;
5221  foreach ( $parts as $part ) {
5222  $part = trim( $part );
5223  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5224  $validated = false;
5225  if ( isset( $paramMap[$magicName] ) ) {
5226  list( $type, $paramName ) = $paramMap[$magicName];
5227 
5228  # Special case; width and height come in one variable together
5229  if ( $type === 'handler' && $paramName === 'width' ) {
5230  $parsedWidthParam = self::parseWidthParam( $value );
5231  if ( isset( $parsedWidthParam['width'] ) ) {
5232  $width = $parsedWidthParam['width'];
5233  if ( $handler->validateParam( 'width', $width ) ) {
5234  $params[$type]['width'] = $width;
5235  $validated = true;
5236  }
5237  }
5238  if ( isset( $parsedWidthParam['height'] ) ) {
5239  $height = $parsedWidthParam['height'];
5240  if ( $handler->validateParam( 'height', $height ) ) {
5241  $params[$type]['height'] = $height;
5242  $validated = true;
5243  }
5244  }
5245  # else no validation -- T15436
5246  } else {
5247  if ( $type === 'handler' ) {
5248  # Validate handler parameter
5249  $validated = $handler->validateParam( $paramName, $value );
5250  } else {
5251  # Validate internal parameters
5252  switch ( $paramName ) {
5253  case 'manualthumb':
5254  case 'alt':
5255  case 'class':
5256  # @todo FIXME: Possibly check validity here for
5257  # manualthumb? downstream behavior seems odd with
5258  # missing manual thumbs.
5259  $validated = true;
5260  $value = $this->stripAltText( $value, $holders );
5261  break;
5262  case 'link':
5263  $chars = self::EXT_LINK_URL_CLASS;
5264  $addr = self::EXT_LINK_ADDR;
5265  $prots = $this->mUrlProtocols;
5266  if ( $value === '' ) {
5267  $paramName = 'no-link';
5268  $value = true;
5269  $validated = true;
5270  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5271  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5272  $paramName = 'link-url';
5273  $this->mOutput->addExternalLink( $value );
5274  if ( $this->mOptions->getExternalLinkTarget() ) {
5275  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5276  }
5277  $validated = true;
5278  }
5279  } else {
5280  $linkTitle = Title::newFromText( $value );
5281  if ( $linkTitle ) {
5282  $paramName = 'link-title';
5283  $value = $linkTitle;
5284  $this->mOutput->addLink( $linkTitle );
5285  $validated = true;
5286  }
5287  }
5288  break;
5289  case 'frameless':
5290  case 'framed':
5291  case 'thumbnail':
5292  // use first appearing option, discard others.
5293  $validated = !$seenformat;
5294  $seenformat = true;
5295  break;
5296  default:
5297  # Most other things appear to be empty or numeric...
5298  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5299  }
5300  }
5301 
5302  if ( $validated ) {
5303  $params[$type][$paramName] = $value;
5304  }
5305  }
5306  }
5307  if ( !$validated ) {
5308  $caption = $part;
5309  }
5310  }
5311 
5312  # Process alignment parameters
5313  if ( $params['horizAlign'] ) {
5314  $params['frame']['align'] = key( $params['horizAlign'] );
5315  }
5316  if ( $params['vertAlign'] ) {
5317  $params['frame']['valign'] = key( $params['vertAlign'] );
5318  }
5319 
5320  $params['frame']['caption'] = $caption;
5321 
5322  # Will the image be presented in a frame, with the caption below?
5323  $imageIsFramed = isset( $params['frame']['frame'] )
5324  || isset( $params['frame']['framed'] )
5325  || isset( $params['frame']['thumbnail'] )
5326  || isset( $params['frame']['manualthumb'] );
5327 
5328  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5329  # came to also set the caption, ordinary text after the image -- which
5330  # makes no sense, because that just repeats the text multiple times in
5331  # screen readers. It *also* came to set the title attribute.
5332  # Now that we have an alt attribute, we should not set the alt text to
5333  # equal the caption: that's worse than useless, it just repeats the
5334  # text. This is the framed/thumbnail case. If there's no caption, we
5335  # use the unnamed parameter for alt text as well, just for the time be-
5336  # ing, if the unnamed param is set and the alt param is not.
5337  # For the future, we need to figure out if we want to tweak this more,
5338  # e.g., introducing a title= parameter for the title; ignoring the un-
5339  # named parameter entirely for images without a caption; adding an ex-
5340  # plicit caption= parameter and preserving the old magic unnamed para-
5341  # meter for BC; ...
5342  if ( $imageIsFramed ) { # Framed image
5343  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5344  # No caption or alt text, add the filename as the alt text so
5345  # that screen readers at least get some description of the image
5346  $params['frame']['alt'] = $title->getText();
5347  }
5348  # Do not set $params['frame']['title'] because tooltips don't make sense
5349  # for framed images
5350  } else { # Inline image
5351  if ( !isset( $params['frame']['alt'] ) ) {
5352  # No alt text, use the "caption" for the alt text
5353  if ( $caption !== '' ) {
5354  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5355  } else {
5356  # No caption, fall back to using the filename for the
5357  # alt text
5358  $params['frame']['alt'] = $title->getText();
5359  }
5360  }
5361  # Use the "caption" for the tooltip text
5362  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5363  }
5364 
5365  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5366 
5367  # Linker does the rest
5368  $time = isset( $options['time'] ) ? $options['time'] : false;
5369  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5370  $time, $descQuery, $this->mOptions->getThumbSize() );
5371 
5372  # Give the handler a chance to modify the parser object
5373  if ( $handler ) {
5374  $handler->parserTransformHook( $this, $file );
5375  }
5376 
5377  return $ret;
5378  }
5379 
5385  protected function stripAltText( $caption, $holders ) {
5386  # Strip bad stuff out of the title (tooltip). We can't just use
5387  # replaceLinkHoldersText() here, because if this function is called
5388  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5389  if ( $holders ) {
5390  $tooltip = $holders->replaceText( $caption );
5391  } else {
5392  $tooltip = $this->replaceLinkHoldersText( $caption );
5393  }
5394 
5395  # make sure there are no placeholders in thumbnail attributes
5396  # that are later expanded to html- so expand them now and
5397  # remove the tags
5398  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5399  $tooltip = Sanitizer::stripAllTags( $tooltip );
5400 
5401  return $tooltip;
5402  }
5403 
5409  public function disableCache() {
5410  wfDebug( "Parser output marked as uncacheable.\n" );
5411  if ( !$this->mOutput ) {
5412  throw new MWException( __METHOD__ .
5413  " can only be called when actually parsing something" );
5414  }
5415  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5416  }
5417 
5426  public function attributeStripCallback( &$text, $frame = false ) {
5427  $text = $this->replaceVariables( $text, $frame );
5428  $text = $this->mStripState->unstripBoth( $text );
5429  return $text;
5430  }
5431 
5437  public function getTags() {
5438  return array_merge(
5439  array_keys( $this->mTransparentTagHooks ),
5440  array_keys( $this->mTagHooks ),
5441  array_keys( $this->mFunctionTagHooks )
5442  );
5443  }
5444 
5455  public function replaceTransparentTags( $text ) {
5456  $matches = [];
5457  $elements = array_keys( $this->mTransparentTagHooks );
5458  $text = self::extractTagsAndParams( $elements, $text, $matches );
5459  $replacements = [];
5460 
5461  foreach ( $matches as $marker => $data ) {
5462  list( $element, $content, $params, $tag ) = $data;
5463  $tagName = strtolower( $element );
5464  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5465  $output = call_user_func_array(
5466  $this->mTransparentTagHooks[$tagName],
5467  [ $content, $params, $this ]
5468  );
5469  } else {
5470  $output = $tag;
5471  }
5472  $replacements[$marker] = $output;
5473  }
5474  return strtr( $text, $replacements );
5475  }
5476 
5506  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5507  global $wgTitle; # not generally used but removes an ugly failure mode
5508 
5509  $magicScopeVariable = $this->lock();
5510  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5511  $outText = '';
5512  $frame = $this->getPreprocessor()->newFrame();
5513 
5514  # Process section extraction flags
5515  $flags = 0;
5516  $sectionParts = explode( '-', $sectionId );
5517  $sectionIndex = array_pop( $sectionParts );
5518  foreach ( $sectionParts as $part ) {
5519  if ( $part === 'T' ) {
5520  $flags |= self::PTD_FOR_INCLUSION;
5521  }
5522  }
5523 
5524  # Check for empty input
5525  if ( strval( $text ) === '' ) {
5526  # Only sections 0 and T-0 exist in an empty document
5527  if ( $sectionIndex == 0 ) {
5528  if ( $mode === 'get' ) {
5529  return '';
5530  } else {
5531  return $newText;
5532  }
5533  } else {
5534  if ( $mode === 'get' ) {
5535  return $newText;
5536  } else {
5537  return $text;
5538  }
5539  }
5540  }
5541 
5542  # Preprocess the text
5543  $root = $this->preprocessToDom( $text, $flags );
5544 
5545  # <h> nodes indicate section breaks
5546  # They can only occur at the top level, so we can find them by iterating the root's children
5547  $node = $root->getFirstChild();
5548 
5549  # Find the target section
5550  if ( $sectionIndex == 0 ) {
5551  # Section zero doesn't nest, level=big
5552  $targetLevel = 1000;
5553  } else {
5554  while ( $node ) {
5555  if ( $node->getName() === 'h' ) {
5556  $bits = $node->splitHeading();
5557  if ( $bits['i'] == $sectionIndex ) {
5558  $targetLevel = $bits['level'];
5559  break;
5560  }
5561  }
5562  if ( $mode === 'replace' ) {
5563  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5564  }
5565  $node = $node->getNextSibling();
5566  }
5567  }
5568 
5569  if ( !$node ) {
5570  # Not found
5571  if ( $mode === 'get' ) {
5572  return $newText;
5573  } else {
5574  return $text;
5575  }
5576  }
5577 
5578  # Find the end of the section, including nested sections
5579  do {
5580  if ( $node->getName() === 'h' ) {
5581  $bits = $node->splitHeading();
5582  $curLevel = $bits['level'];
5583  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5584  break;
5585  }
5586  }
5587  if ( $mode === 'get' ) {
5588  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5589  }
5590  $node = $node->getNextSibling();
5591  } while ( $node );
5592 
5593  # Write out the remainder (in replace mode only)
5594  if ( $mode === 'replace' ) {
5595  # Output the replacement text
5596  # Add two newlines on -- trailing whitespace in $newText is conventionally
5597  # stripped by the editor, so we need both newlines to restore the paragraph gap
5598  # Only add trailing whitespace if there is newText
5599  if ( $newText != "" ) {
5600  $outText .= $newText . "\n\n";
5601  }
5602 
5603  while ( $node ) {
5604  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5605  $node = $node->getNextSibling();
5606  }
5607  }
5608 
5609  if ( is_string( $outText ) ) {
5610  # Re-insert stripped tags
5611  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5612  }
5613 
5614  return $outText;
5615  }
5616 
5631  public function getSection( $text, $sectionId, $defaultText = '' ) {
5632  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5633  }
5634 
5647  public function replaceSection( $oldText, $sectionId, $newText ) {
5648  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5649  }
5650 
5656  public function getRevisionId() {
5657  return $this->mRevisionId;
5658  }
5659 
5666  public function getRevisionObject() {
5667  if ( !is_null( $this->mRevisionObject ) ) {
5668  return $this->mRevisionObject;
5669  }
5670  if ( is_null( $this->mRevisionId ) ) {
5671  return null;
5672  }
5673 
5674  $rev = call_user_func(
5675  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5676  );
5677 
5678  # If the parse is for a new revision, then the callback should have
5679  # already been set to force the object and should match mRevisionId.
5680  # If not, try to fetch by mRevisionId for sanity.
5681  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5682  $rev = Revision::newFromId( $this->mRevisionId );
5683  }
5684 
5685  $this->mRevisionObject = $rev;
5686 
5687  return $this->mRevisionObject;
5688  }
5689 
5695  public function getRevisionTimestamp() {
5696  if ( is_null( $this->mRevisionTimestamp ) ) {
5698 
5699  $revObject = $this->getRevisionObject();
5700  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5701 
5702  # The cryptic '' timezone parameter tells to use the site-default
5703  # timezone offset instead of the user settings.
5704  # Since this value will be saved into the parser cache, served
5705  # to other users, and potentially even used inside links and such,
5706  # it needs to be consistent for all visitors.
5707  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5708 
5709  }
5710  return $this->mRevisionTimestamp;
5711  }
5712 
5718  public function getRevisionUser() {
5719  if ( is_null( $this->mRevisionUser ) ) {
5720  $revObject = $this->getRevisionObject();
5721 
5722  # if this template is subst: the revision id will be blank,
5723  # so just use the current user's name
5724  if ( $revObject ) {
5725  $this->mRevisionUser = $revObject->getUserText();
5726  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5727  $this->mRevisionUser = $this->getUser()->getName();
5728  }
5729  }
5730  return $this->mRevisionUser;
5731  }
5732 
5738  public function getRevisionSize() {
5739  if ( is_null( $this->mRevisionSize ) ) {
5740  $revObject = $this->getRevisionObject();
5741 
5742  # if this variable is subst: the revision id will be blank,
5743  # so just use the parser input size, because the own substituation
5744  # will change the size.
5745  if ( $revObject ) {
5746  $this->mRevisionSize = $revObject->getSize();
5747  } else {
5748  $this->mRevisionSize = $this->mInputSize;
5749  }
5750  }
5751  return $this->mRevisionSize;
5752  }
5753 
5759  public function setDefaultSort( $sort ) {
5760  $this->mDefaultSort = $sort;
5761  $this->mOutput->setProperty( 'defaultsort', $sort );
5762  }
5763 
5774  public function getDefaultSort() {
5775  if ( $this->mDefaultSort !== false ) {
5776  return $this->mDefaultSort;
5777  } else {
5778  return '';
5779  }
5780  }
5781 
5788  public function getCustomDefaultSort() {
5789  return $this->mDefaultSort;
5790  }
5791 
5792  private static function getSectionNameFromStrippedText( $text ) {
5793  $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5794  $text = Sanitizer::decodeCharReferences( $text );
5795  $text = self::normalizeSectionName( $text );
5796  return $text;
5797  }
5798 
5799  private static function makeAnchor( $sectionName ) {
5800  return '#' . Sanitizer::escapeIdForLink( $sectionName );
5801  }
5802 
5803  private static function makeLegacyAnchor( $sectionName ) {
5805  if ( isset( $wgFragmentMode[1] ) && $wgFragmentMode[1] === 'legacy' ) {
5806  // ForAttribute() and ForLink() are the same for legacy encoding
5807  $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
5808  } else {
5809  $id = Sanitizer::escapeIdForLink( $sectionName );
5810  }
5811 
5812  return "#$id";
5813  }
5814 
5823  public function guessSectionNameFromWikiText( $text ) {
5824  # Strip out wikitext links(they break the anchor)
5825  $text = $this->stripSectionName( $text );
5826  $sectionName = self::getSectionNameFromStrippedText( $text );
5827  return self::makeAnchor( $sectionName );
5828  }
5829 
5839  public function guessLegacySectionNameFromWikiText( $text ) {
5840  # Strip out wikitext links(they break the anchor)
5841  $text = $this->stripSectionName( $text );
5842  $sectionName = self::getSectionNameFromStrippedText( $text );
5843  return self::makeLegacyAnchor( $sectionName );
5844  }
5845 
5851  public static function guessSectionNameFromStrippedText( $text ) {
5852  $sectionName = self::getSectionNameFromStrippedText( $text );
5853  return self::makeAnchor( $sectionName );
5854  }
5855 
5862  private static function normalizeSectionName( $text ) {
5863  # T90902: ensure the same normalization is applied for IDs as to links
5864  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
5865  try {
5866 
5867  $parts = $titleParser->splitTitleString( "#$text" );
5868  } catch ( MalformedTitleException $ex ) {
5869  return $text;
5870  }
5871  return $parts['fragment'];
5872  }
5873 
5888  public function stripSectionName( $text ) {
5889  # Strip internal link markup
5890  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5891  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5892 
5893  # Strip external link markup
5894  # @todo FIXME: Not tolerant to blank link text
5895  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5896  # on how many empty links there are on the page - need to figure that out.
5897  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5898 
5899  # Parse wikitext quotes (italics & bold)
5900  $text = $this->doQuotes( $text );
5901 
5902  # Strip HTML tags
5903  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5904  return $text;
5905  }
5906 
5917  public function testSrvus( $text, Title $title, ParserOptions $options,
5918  $outputType = self::OT_HTML
5919  ) {
5920  $magicScopeVariable = $this->lock();
5921  $this->startParse( $title, $options, $outputType, true );
5922 
5923  $text = $this->replaceVariables( $text );
5924  $text = $this->mStripState->unstripBoth( $text );
5925  $text = Sanitizer::removeHTMLtags( $text );
5926  return $text;
5927  }
5928 
5935  public function testPst( $text, Title $title, ParserOptions $options ) {
5936  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5937  }
5938 
5945  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5946  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5947  }
5948 
5965  public function markerSkipCallback( $s, $callback ) {
5966  $i = 0;
5967  $out = '';
5968  while ( $i < strlen( $s ) ) {
5969  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5970  if ( $markerStart === false ) {
5971  $out .= call_user_func( $callback, substr( $s, $i ) );
5972  break;
5973  } else {
5974  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5975  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5976  if ( $markerEnd === false ) {
5977  $out .= substr( $s, $markerStart );
5978  break;
5979  } else {
5980  $markerEnd += strlen( self::MARKER_SUFFIX );
5981  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5982  $i = $markerEnd;
5983  }
5984  }
5985  }
5986  return $out;
5987  }
5988 
5995  public function killMarkers( $text ) {
5996  return $this->mStripState->killMarkers( $text );
5997  }
5998 
6016  public function serializeHalfParsedText( $text ) {
6017  wfDeprecated( __METHOD__, '1.31' );
6018  $data = [
6019  'text' => $text,
6020  'version' => self::HALF_PARSED_VERSION,
6021  'stripState' => $this->mStripState->getSubState( $text ),
6022  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6023  ];
6024  return $data;
6025  }
6026 
6043  public function unserializeHalfParsedText( $data ) {
6044  wfDeprecated( __METHOD__, '1.31' );
6045  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6046  throw new MWException( __METHOD__ . ': invalid version' );
6047  }
6048 
6049  # First, extract the strip state.
6050  $texts = [ $data['text'] ];
6051  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6052 
6053  # Now renumber links
6054  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6055 
6056  # Should be good to go.
6057  return $texts[0];
6058  }
6059 
6070  public function isValidHalfParsedText( $data ) {
6071  wfDeprecated( __METHOD__, '1.31' );
6072  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6073  }
6074 
6084  public static function parseWidthParam( $value, $parseHeight = true ) {
6085  $parsedWidthParam = [];
6086  if ( $value === '' ) {
6087  return $parsedWidthParam;
6088  }
6089  $m = [];
6090  # (T15500) In both cases (width/height and width only),
6091  # permit trailing "px" for backward compatibility.
6092  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6093  $width = intval( $m[1] );
6094  $height = intval( $m[2] );
6095  $parsedWidthParam['width'] = $width;
6096  $parsedWidthParam['height'] = $height;
6097  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6098  $width = intval( $value );
6099  $parsedWidthParam['width'] = $width;
6100  }
6101  return $parsedWidthParam;
6102  }
6103 
6113  protected function lock() {
6114  if ( $this->mInParse ) {
6115  throw new MWException( "Parser state cleared while parsing. "
6116  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6117  }
6118 
6119  // Save the backtrace when locking, so that if some code tries locking again,
6120  // we can print the lock owner's backtrace for easier debugging
6121  $e = new Exception;
6122  $this->mInParse = $e->getTraceAsString();
6123 
6124  $recursiveCheck = new ScopedCallback( function () {
6125  $this->mInParse = false;
6126  } );
6127 
6128  return $recursiveCheck;
6129  }
6130 
6141  public static function stripOuterParagraph( $html ) {
6142  $m = [];
6143  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6144  if ( strpos( $m[1], '</p>' ) === false ) {
6145  $html = $m[1];
6146  }
6147  }
6148 
6149  return $html;
6150  }
6151 
6162  public function getFreshParser() {
6164  if ( $this->mInParse ) {
6165  return new $wgParserConf['class']( $wgParserConf );
6166  } else {
6167  return $this;
6168  }
6169  }
6170 
6177  public function enableOOUI() {
6179  $this->mOutput->setEnableOOUI( true );
6180  }
6181 }
OT_MSG
const OT_MSG
Definition: Defines.php:188
SiteStats\articles
static articles()
Definition: SiteStats.php:103
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:40
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
$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:244
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:1987
if
if($IP===false)
Definition: cleanupArchiveUserText.php:4
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
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
Revision\newKnownCurrent
static newKnownCurrent(IDatabase $db, $pageIdOrTitle, $revId=0)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1288
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:273
PPFrame\STRIP_COMMENTS
const STRIP_COMMENTS
Definition: Preprocessor.php:169
MWNamespace\isNonincludable
static isNonincludable( $index)
It is not possible to use pages from this namespace as template?
Definition: MWNamespace.php:460
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
SiteStats\users
static users()
Definition: SiteStats.php:121
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2604
SiteStats\activeUsers
static activeUsers()
Definition: SiteStats.php:130
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:186
PPFrame\NO_ARGS
const NO_ARGS
Definition: Preprocessor.php:167
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:1687
$wgParserConf
$wgParserConf
Parser configuration.
Definition: DefaultSettings.php:4149
captcha-old.count
count
Definition: captcha-old.py:249
Linker\tocIndent
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1519
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
$wgTranscludeCacheExpiry
$wgTranscludeCacheExpiry
Expiry time for transcluded templates cached in transcache database table.
Definition: DefaultSettings.php:4394
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
$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 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name '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:1985
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1968
SiteStats\pages
static pages()
Definition: SiteStats.php:112
$wgNoFollowDomainExceptions
$wgNoFollowDomainExceptions
If this is set to an array of domains, external links to these domain names (or any subdomains) will ...
Definition: DefaultSettings.php:4359
$wgShowHostnames
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.
Definition: DefaultSettings.php:6274
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:340
SiteStats\numberingroup
static numberingroup( $group)
Find the number of users in a given user group.
Definition: SiteStats.php:150
$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:199
MagicWord\get
static & get( $id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:277
OT_PREPROCESS
const OT_PREPROCESS
Definition: Defines.php:187
NS_FILE
const NS_FILE
Definition: Defines.php:71
OT_PLAIN
const OT_PLAIN
Definition: Defines.php:189
$params
$params
Definition: styleTest.css.php:40
wfHostname
wfHostname()
Fetch server name for use in error reporting etc.
Definition: GlobalFunctions.php:1408
NS_TEMPLATE
const NS_TEMPLATE
Definition: Defines.php:75
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:591
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:3005
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:588
$s
$s
Definition: mergeMessageFileList.php:187
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
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
$wgFragmentMode
$wgFragmentMode
How should section IDs be encoded? This array can contain 1 or 2 elements, each of them can be one of...
Definition: DefaultSettings.php:3394
page
target page
Definition: All_system_messages.txt:1267
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
$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:58
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
Linker\tocLine
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1545
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:1075
$wgStylePath
$wgStylePath
The URL path of the skins directory.
Definition: DefaultSettings.php:200
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:168
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
Preprocessor
Definition: Preprocessor.php:29
later
If you want to remove the page from your watchlist later
Definition: All_system_messages.txt:361
$dbr
$dbr
Definition: testCompression.php:50
SiteStats\images
static images()
Definition: SiteStats.php:139
StringUtils\replaceMarkup
static replaceMarkup( $search, $replace, $text)
More or less "markup-safe" str_replace() Ignores any instances of the separator inside <....
Definition: StringUtils.php:286
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:133
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:54
$wgEnableScaryTranscluding
$wgEnableScaryTranscluding
Enable interwiki transcluding.
Definition: DefaultSettings.php:4388
MagicWord\getVariableIDs
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:291
$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:1987
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:314
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1111
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
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2800
wfUrlProtocolsWithoutProtRel
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Definition: GlobalFunctions.php:772
Linker\tocList
static tocList( $toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1581
$matches
$matches
Definition: NoLocalSettings.php:24
in
null for the wiki Added in
Definition: hooks.txt:1591
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:323
PPNode
There are three types of nodes:
Definition: Preprocessor.php:359
LinkHolderArray
Definition: LinkHolderArray.php:27
PPFrame\RECOVER_ORIG
const RECOVER_ORIG
Definition: Preprocessor.php:174
$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:1987
Linker\makeHeadline
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1642
not
if not
Definition: COPYING.txt:307
Linker\tocLineEnd
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1569
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:368
form
null means default in associative array form
Definition: hooks.txt:1987
$lines
$lines
Definition: router.php:61
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2595
MWTimestamp\getInstance
static getInstance( $ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:39
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:843
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1795
OT_WIKI
const OT_WIKI
Definition: Defines.php:186
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:534
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$output
$output
Definition: SyntaxHighlight.php:338
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:30
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1997
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:79
RequestContext
Group all the pieces relevant to the context of a request into one instance.
Definition: RequestContext.php:32
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:982
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
$sort
$sort
Definition: profileinfo.php:317
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:1665
or
or
Definition: COPYING.txt:140
wfUrlProtocols
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
Definition: GlobalFunctions.php:727
SpecialVersion\getVersion
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
Definition: SpecialVersion.php:271
$line
$line
Definition: cdb.php:59
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:2163
$value
$value
Definition: styleTest.css.php:45
$wgNoFollowNsExceptions
$wgNoFollowNsExceptions
Namespaces in which $wgNoFollowLinks doesn't apply.
Definition: DefaultSettings.php:4344
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:84
$wgNoFollowLinks
$wgNoFollowLinks
If true, external URL links in wiki text will be given the rel="nofollow" attribute as a hint to sear...
Definition: DefaultSettings.php:4338
$wgServerName
$wgServerName
Server name.
Definition: DefaultSettings.php:121
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:53
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:105
PPFrame
Definition: Preprocessor.php:166
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2866
$wgSitename
$wgSitename
Name of the site.
Definition: DefaultSettings.php:79
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:56
OutputPage\setupOOUI
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
Definition: OutputPage.php:3904
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1631
$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:1987
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:324
$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:1649
tag
</code > tag
Definition: citeParserTests.txt:225
plain
either a plain
Definition: hooks.txt:2048
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:2841
SFH_NO_HASH
const SFH_NO_HASH
Definition: Defines.php:198
$wgExtraInterlanguageLinkPrefixes
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i....
Definition: DefaultSettings.php:2908
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:45
$args
if( $line===false) $args
Definition: cdb.php:64
OT_HTML
const OT_HTML
Definition: Defines.php:185
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:1334
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
MagicWord\getSubstIDs
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:304
wfMatchesDomainList
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
Definition: GlobalFunctions.php:948
MalformedTitleException
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Definition: MalformedTitleException.php:25
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1987
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:731
used
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:115
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:1767
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:1767
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:1530
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
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:783
StringUtils\delimiterReplace
static delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags='')
Perform an operation equivalent to preg_replace() with flags.
Definition: StringUtils.php:245
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3005
MagicWord\getDoubleUnderscoreArray
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:327
TextContent\normalizeLineEndings
static normalizeLineEndings( $text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace.
Definition: TextContent.php:168
captcha-old.parser
parser
Definition: captcha-old.py:210
Linker\normalizeSubpageLink
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition: Linker.php:1369
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
$wgMaxTocLevel
$wgMaxTocLevel
Maximum indent level of toc.
Definition: DefaultSettings.php:4157
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1255
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:101
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:73
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:369
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
$wgMaxSigChars
$wgMaxSigChars
Maximum number of Unicode characters in signature.
Definition: DefaultSettings.php:4801
$t
$t
Definition: testCompression.php:69
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:623
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
RawMessage
Variant of the Message class.
Definition: RawMessage.php:34
$wgScriptPath
$wgScriptPath
The path we should point to.
Definition: DefaultSettings.php:137
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
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
$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:271
SiteStats\edits
static edits()
Definition: SiteStats.php:94
$buffer
$buffer
Definition: mwdoc-filter.php:49
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
MWHttpRequest\factory
static factory( $url, array $options=null, $caller=__METHOD__)
Generate a new request object Deprecated:
Definition: MWHttpRequest.php:184
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
$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
$type
$type
Definition: testCompression.php:48
MWTidy\tidy
static tidy( $text)
Interface with html tidy.
Definition: MWTidy.php:46